From 6763fdbbbb1c1a61e12d15b1e4fd9a36ed52d5e2 Mon Sep 17 00:00:00 2001 From: DevelopmentCats Date: Tue, 24 Jun 2025 20:12:39 +0000 Subject: [PATCH] feat: Add Documentation for Coder Modules --- MODULE.md | 1200 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1200 insertions(+) create mode 100644 MODULE.md diff --git a/MODULE.md b/MODULE.md new file mode 100644 index 00000000..44a204f5 --- /dev/null +++ b/MODULE.md @@ -0,0 +1,1200 @@ +# Coder Modules: Comprehensive Documentation + +_Technical reference for understanding, developing, and implementing Coder modules in workspace environments_ + +## Table of Contents + +1. [Foundation & Core Concepts](#foundation--core-concepts) +2. [How Coder Modules Work](#how-coder-modules-work) +3. [Module Architecture & Registry](#module-architecture--registry) +4. [Module Implementation Patterns](#module-implementation-patterns) +5. [Reference & Resources](#reference--resources) + +--- + +## Foundation & Core Concepts + +### What are Coder Modules? + +Coder modules are reusable Terraform configurations that extend Coder workspaces with development tools, integrations, and functionality. Each module encapsulates a specific capability and can be integrated into workspace templates to provide targeted functionality. + +Modules are building blocks that add functionality to Coder workspaces. Instead of manually configuring development tools, you include a module and it handles the setup automatically. + +### Key Features + +Modules provide several key features that make Coder workspaces powerful and scalable: + +**Reusability**: Write once, use everywhere. Modules eliminate configuration duplication across multiple templates and environments. + +**Consistency**: Standardized implementations ensure uniform development environments and enforce best practices across teams. + +**Composability**: Mix and match modules to create comprehensive development environments tailored to specific needs. + +**Maintainability**: Centralized module development means bug fixes and improvements benefit all users, with controlled updates ensuring stability and predictable change management. + +**Speed**: Automated setup and configuration reduces manual overhead and accelerates development environment creation. + +### Ecosystem Integration + +Coder modules operate within a structured ecosystem: + +- **Coder Platform**: Core infrastructure management and workspace orchestration +- **Templates**: Terraform configurations defining workspace infrastructure and module integration +- **Modules**: Reusable components providing specific functionality extensions +- **Registry**: Centralized repository for module discovery and distribution +- **Agents**: Runtime components executing module-defined operations within workspaces + +The relationship between these components enables scalable workspace management: + +``` + ┌─────────────────────────┐ + │ Coder Platform │ + │ │ + │ Orchestrates entire │ + │ workspace lifecycle │ + └───────────┬─────────────┘ + │ + │ Manages + ▼ + ┌─────────────────────────┐ + │ Module Registry │◄─── Publishes + │ │ modules + │ Hosts and distributes │ + │ reusable modules │ + └───────────┬─────────────┘ + │ + │ Provides modules + ▼ + ┌─────────────────────────┐ + │ Templates │◄─── Users create + │ │ templates + │ Define infrastructure │ + │ and integrate modules │ + └───────────┬─────────────┘ + │ + │ Provisions + ▼ + ┌─────────────────────────┐ + │ Workspaces │◄─── Developers use + │ │ workspaces + │ Running development │ + │ environments │ + └─────────────────────────┘ +``` + +### Terraform Foundation + +Coder modules are implemented as Terraform modules using HashiCorp's infrastructure-as-code framework. The [Coder Terraform provider](https://registry.terraform.io/providers/coder/coder/latest/docs) extends standard Terraform functionality with workspace-specific resources: + +**Core Module Resources:** + +- [`coder_script`](https://registry.terraform.io/providers/coder/coder/latest/docs/resources/script): Executes scripts during workspace lifecycle events +- [`coder_app`](https://registry.terraform.io/providers/coder/coder/latest/docs/resources/app): Creates accessible applications within the workspace interface +- [`coder_env`](https://registry.terraform.io/providers/coder/coder/latest/docs/resources/env): Sets environment variables in the workspace + +**Template Resources (rarely used in modules):** + +- [`coder_agent`](https://registry.terraform.io/providers/coder/coder/latest/docs/resources/agent): Defines agent configuration for workspace runtime +- [`coder_parameter`](https://registry.terraform.io/providers/coder/coder/latest/docs/resources/parameter): Collects user input during workspace provisioning +- [`coder_metadata`](https://registry.terraform.io/providers/coder/coder/latest/docs/resources/metadata): Displays resource information in the workspace interface + +**Advanced Resources:** + +- [`coder_agent_instance`](https://registry.terraform.io/providers/coder/coder/latest/docs/resources/agent_instance): Manages agent instance connections +- [`coder_ai_task`](https://registry.terraform.io/providers/coder/coder/latest/docs/resources/ai_task): Defines AI-powered task automation +- [`coder_devcontainer`](https://registry.terraform.io/providers/coder/coder/latest/docs/resources/devcontainer): Configures development container environments + +Modules use standard Terraform syntax plus these Coder-specific resources to integrate with workspaces. + +### Provider and Terraform Requirements + +Modules specify required Terraform and provider versions: + +```tf +terraform { + required_version = ">= 1.0" + + required_providers { + coder = { + source = "coder/coder" + version = ">= 2.5" + } + } +} +``` + +This ensures compatibility with minimum versions of Terraform and the Coder provider. + +**Current Version Standards:** + +- **Latest Coder Provider**: v2.8.0 (June 2025) +- **Recommended Minimum**: `>= 2.5` (used by most current modules) +- **Legacy Modules**: Some older modules still use lower versions (0.12+) but should be updated when possible + +New modules should use `>= 2.5` to ensure access to modern features like app groups, prebuilt workspaces, and improved validation. + +--- + +## How Coder Modules Work + +### Module Lifecycle + +Module execution follows a defined lifecycle within workspace operations: + +1. **Template Processing**: Templates reference modules and define configuration parameters +2. **Resource Planning**: Terraform analyzes module dependencies and resource requirements +3. **Resource Provisioning**: Terraform creates infrastructure and workspace resources +4. **Agent Initialization**: Coder agent starts and processes module-defined scripts +5. **Runtime Operation**: Modules provide ongoing functionality through applications and integrations + +### Core Module Components + +#### Input Variables + +Modules define input variables to control behavior and configuration: + +```tf +variable "agent_id" { + type = string + description = "The ID of a Coder agent." +} + +variable "port" { + type = number + description = "The port to run VS Code Web on." + default = 13338 +} + +variable "extensions" { + type = list(string) + description = "A list of extensions to install." + default = [] +} + +variable "accept_license" { + type = bool + description = "Accept the VS Code Server license. https://code.visualstudio.com/license/server" + default = false + validation { + condition = var.accept_license == true + error_message = "You must accept the VS Code license agreement by setting accept_license=true." + } +} + +variable "share" { + type = string + default = "owner" + validation { + condition = var.share == "owner" || var.share == "authenticated" || var.share == "public" + error_message = "Incorrect value. Please set either 'owner', 'authenticated', or 'public'." + } +} +``` + +#### Variable Validation Patterns + +Modules use validation blocks to catch configuration errors early: + +```tf +# Example from jetbrains-gateway module +variable "arch" { + type = string + description = "The target architecture of the workspace" + default = "amd64" + validation { + condition = contains(["amd64", "arm64"], var.arch) + error_message = "Architecture must be either 'amd64' or 'arm64'." + } +} + +variable "channel" { + type = string + description = "JetBrains IDE release channel. Valid values are release and eap." + default = "release" + validation { + condition = can(regex("^(release|eap)$", var.channel)) + error_message = "The channel must be either release or eap." + } +} + +variable "jetbrains_ides" { + type = list(string) + description = "The list of IDE product codes." + default = ["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD", "RR"] + validation { + condition = ( + alltrue([ + for code in var.jetbrains_ides : contains(["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD", "RR"], code) + ]) + ) + error_message = "The jetbrains_ides must be a list of valid product codes. Valid product codes are ${join(",", ["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD", "RR"])}." + } +} +``` + +#### Sensitive Variables + +Modules handling credentials or API keys use sensitive variables: + +```tf +# Example from aider module +variable "ai_api_key" { + type = string + description = "API key for the selected AI provider." + default = "" + sensitive = true +} + +# Example from windows-rdp module +variable "admin_password" { + type = string + default = "coderRDP!" + sensitive = true +} +``` + +#### Multi-line Descriptions + +Complex variable descriptions use heredoc syntax for better readability: + +```tf +# Example from code-server module +variable "subdomain" { + type = bool + description = <<-EOT + Determines whether the app will be accessed via it's own subdomain or whether it will be accessed via a path on Coder. + If wildcards have not been setup by the administrator then apps with "subdomain" set to true will not be accessible. + EOT + default = false +} + +# Example from jfrog-oauth module +variable "package_managers" { + type = object({ + npm = optional(list(string), []) + go = optional(list(string), []) + pypi = optional(list(string), []) + docker = optional(list(string), []) + }) + description = <<-EOF + A map of package manager names to their respective artifactory repositories. Unused package managers can be omitted. + For example: + { + npm = ["GLOBAL_NPM_REPO_KEY", "@SCOPED:NPM_REPO_KEY"] + go = ["YOUR_GO_REPO_KEY", "ANOTHER_GO_REPO_KEY"] + pypi = ["YOUR_PYPI_REPO_KEY", "ANOTHER_PYPI_REPO_KEY"] + docker = ["YOUR_DOCKER_REPO_KEY", "ANOTHER_DOCKER_REPO_KEY"] + } + EOF +} +``` + +#### Resource Definitions + +Modules create Coder-specific resources to implement functionality: + +```tf +resource "coder_script" "vscode-web" { + agent_id = var.agent_id + display_name = "VS Code Web" + icon = "/icon/code.svg" + script = templatefile("${path.module}/run.sh", { + PORT : var.port, + LOG_PATH : var.log_path, + INSTALL_PREFIX : var.install_prefix, + EXTENSIONS : join(",", var.extensions), + TELEMETRY_LEVEL : var.telemetry_level, + SETTINGS : replace(jsonencode(var.settings), "\"", "\\\""), + FOLDER : var.folder, + SERVER_BASE_PATH : local.server_base_path, + COMMIT_ID : var.commit_id, + }) + run_on_start = true +} + +resource "coder_app" "vscode-web" { + agent_id = var.agent_id + slug = var.slug + display_name = var.display_name + url = local.url + icon = "/icon/code.svg" + subdomain = var.subdomain + share = var.share + order = var.order + group = var.group + + healthcheck { + url = local.healthcheck_url + interval = 5 + threshold = 6 + } +} +``` + +#### Lifecycle Preconditions + +Advanced modules use lifecycle preconditions to validate configuration at the Terraform level: + +```tf +# Example from code-server module +resource "coder_script" "code-server" { + agent_id = var.agent_id + display_name = "code-server" + script = templatefile("${path.module}/run.sh", local.template_vars) + run_on_start = true + + lifecycle { + precondition { + condition = !var.offline || length(var.extensions) == 0 + error_message = "Offline mode does not allow extensions to be installed" + } + + precondition { + condition = !var.offline || !var.use_cached + error_message = "Offline and Use Cached can not be used together" + } + } +} +``` + +#### External App Resources + +Some modules create links to external documentation or resources: + +```tf +# Example from windows-rdp module +resource "coder_app" "rdp-docs" { + agent_id = var.agent_id + display_name = "Local RDP Docs" + slug = "rdp-docs" + icon = "https://raw.githubusercontent.com/matifali/logos/main/windows.svg" + url = "https://coder.com/docs/ides/remote-desktops#rdp-desktop" + external = true +} +``` + +#### Output Values + +Most modules don't need output values, but some specialized modules provide them for template integration and inter-module communication. Outputs are typically used by: + +- **Region selector modules** (for passing selected regions to infrastructure) +- **Authentication modules** (for sharing tokens/credentials) +- **Git modules** (for sharing repository information) + +```tf +# Example from git-clone module +output "repo_dir" { + value = local.clone_path + description = "Full path of cloned repo directory" +} + +output "clone_url" { + value = local.clone_url + description = "The exact Git repository URL that will be cloned" +} + +output "folder_name" { + value = local.folder_name + description = "The name of the folder that will be created" +} + +# Example from jetbrains-gateway module +output "identifier" { + value = local.identifier +} + +output "download_link" { + value = local.download_link +} + +output "url" { + value = coder_app.gateway.url +} +``` + +### Module Integration with Templates + +Modules are designed to integrate seamlessly with Coder templates, following established patterns for configuration and lifecycle management. + +#### Parameter Collection + +Most modules receive user input through variables passed from templates. Only specialized modules (like region selectors) collect parameters directly: + +```tf +# Example from gcp-region module +data "coder_parameter" "region" { + name = "gcp_region" + display_name = var.display_name + description = var.description + icon = "/icon/gcp.png" + mutable = var.mutable + default = var.default != null && var.default != "" && (!var.gpu_only || try(local.zones[var.default].gpu, false)) ? var.default : null + order = var.coder_parameter_order + dynamic "option" { + for_each = { + for k, v in local.zones : k => v + if anytrue([for d in var.regions : startswith(k, d)]) && (!var.gpu_only || v.gpu) && (!var.single_zone_per_region || endswith(k, "-a")) + } + content { + icon = try(var.custom_icons[option.key], option.value.icon) + name = try(var.custom_names[option.key], var.single_zone_per_region ? substr(option.value.name, 0, length(option.value.name) - 4) : option.value.name) + description = option.key + value = option.key + } + } +} + +# Template usage example +module "gcp_region" { + source = "registry.coder.com/coder/gcp-region/coder" + default = "us-central1-a" + regions = ["us-central1", "us-west1"] +} +``` + +**Best Practice**: Define parameters in templates and pass values to modules via variables, rather than having modules collect parameters directly. + +#### Template Integration Patterns + +Templates integrate modules using several established patterns: + +**Conditional Loading**: Templates use `start_count` to conditionally load modules based on workspace state, ensuring modules only run when workspaces are active. + +**Simple Composition**: Templates include multiple modules independently, each handling specific functionality without complex interdependencies. + +**Parameter-Driven Configuration**: Templates collect user input via `coder_parameter` and pass values to modules through variables, keeping input collection separate from module implementation. + +**Version Pinning**: Templates pin module versions using version constraints (e.g., `version = "~> 1.0"`) to ensure stability while allowing compatible updates. + +### Advanced Module Capabilities + +#### Common Data Sources + +Most modules use standard data sources to access workspace context: + +```tf +# Essential data sources for workspace context +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} + +# Architecture detection for platform-specific installations +data "coder_provisioner" "me" {} + +# Example usage in modules +locals { + # Platform-specific binary selection + arch_suffix = data.coder_provisioner.me.arch == "arm64" ? "-arm64" : "" + + # Dynamic URL construction + workspace_url = "https://${data.coder_workspace.me.access_url}" + + # User-specific configuration + owner_name = data.coder_workspace_owner.me.name +} + +# Example from jetbrains-gateway module - Architecture-based logic +locals { + download_key = var.arch == "arm64" ? "linuxARM64" : "linux" + + # Complex architecture detection and IDE version handling + effective_jetbrains_ide_versions = { + for k, v in var.jetbrains_ide_versions : k => { + build_number = v.build_number + version = var.arch == "arm64" ? "${v.version}-aarch64" : v.version + } + } +} +``` + +#### Conditional Logic + +Modules can use conditional logic for various purposes including URL construction, resource configuration, and feature toggling: + +```tf +# Example from vscode-web module +locals { + server_base_path = var.subdomain ? "" : format("/@%s/%s/apps/%s/", data.coder_workspace_owner.me.name, data.coder_workspace.me.name, var.slug) + url = var.folder == "" ? "http://localhost:${var.port}${local.server_base_path}" : "http://localhost:${var.port}${local.server_base_path}?folder=${var.folder}" + healthcheck_url = var.subdomain ? "http://localhost:${var.port}/healthz" : "http://localhost:${var.port}${local.server_base_path}/healthz" +} + +# Example from jetbrains-gateway module +locals { + download_key = var.arch == "arm64" ? "linuxARM64" : "linux" + effective_jetbrains_ide_versions = { + for k, v in var.jetbrains_ide_versions : k => { + build_number = v.build_number + version = var.arch == "arm64" ? "${v.version}-aarch64" : v.version + } + } +} +``` + +#### Complex Local Value Processing + +Modules often use sophisticated local value processing for data transformation: + +```tf +# Example from aider module - Environment variable mapping +locals { + provider_env_vars = { + openai = "OPENAI_API_KEY" + anthropic = "ANTHROPIC_API_KEY" + azure = "AZURE_OPENAI_API_KEY" + google = "GOOGLE_API_KEY" + cohere = "COHERE_API_KEY" + mistral = "MISTRAL_API_KEY" + ollama = "OLLAMA_HOST" + custom = var.custom_env_var_name + } + + env_var_name = local.provider_env_vars[var.ai_provider] + + encoded_pre_install_script = var.experiment_pre_install_script != null ? base64encode(var.experiment_pre_install_script) : "" +} + +# Example from jfrog-oauth module - Template processing +locals { + npmrc = templatefile( + "${path.module}/.npmrc.tftpl", + merge( + local.common_values, + { + REPOS = [ + for r in var.package_managers.npm : + strcontains(r, ":") ? zipmap(["SCOPE", "NAME"], ["${split(":", r)[0]}:", split(":", r)[1]]) : { SCOPE = "", NAME = r } + ] + } + ) + ) +} +``` + +#### External Integrations + +Modules can integrate with external services and APIs: + +```tf +# Example from jfrog-oauth module +data "coder_external_auth" "jfrog" { + id = var.external_auth_id +} + +resource "coder_env" "jfrog_ide_access_token" { + count = var.configure_code_server ? 1 : 0 + agent_id = var.agent_id + name = "JFROG_IDE_ACCESS_TOKEN" + value = data.coder_external_auth.jfrog.access_token +} + +resource "coder_env" "goproxy" { + count = length(var.package_managers.go) == 0 ? 0 : 1 + agent_id = var.agent_id + name = "GOPROXY" + value = join(",", [ + for repo in var.package_managers.go : + "https://${local.username}:${data.coder_external_auth.jfrog.access_token}@${local.jfrog_host}/artifactory/api/go/${repo}" + ]) +} +``` + +--- + +## Module Architecture & Registry + +### Module Architecture + +A Coder module implements a standard Terraform module structure with Coder-specific conventions: + +``` +module-name/ +├── main.tf # Primary Terraform configuration +├── README.md # Documentation with structured frontmatter +├── main.test.ts # Automated test suite +└── run.sh # Optional execution script +``` + +### Module Registry and Namespaces + +Modules in the Coder Registry are organized using namespaces, which provide a way to group modules by their maintainers and ensure unique module names across the ecosystem. + +#### Understanding Namespaces + +Each namespace represents a distinct contributor or organization: + +- **`coder`**: The official namespace maintained by the Coder team. All modules in this namespace are verified and maintained with official support. +- **Community namespaces**: Individual contributors and organizations can create their own namespaces (e.g., `thezoker`, `whizus`, `nataindata`) to publish modules. + +#### Module Source Format + +When referencing a module in your template, use the following format: + +``` +registry.coder.com/[namespace]/[module-name]/coder +``` + +For example: + +- Official module: `registry.coder.com/coder/vscode-web/coder` +- Community module: `registry.coder.com/thezoker/nodejs/coder` + +The trailing `/coder` is a required constant that indicates this is a Coder-specific module. + +#### Namespace Directory Structure + +Within the registry repository, modules are organized as: + +``` +registry/ +├── coder/ # Official namespace +│ └── modules/ +│ ├── vscode-web/ +│ ├── cursor/ +│ └── ... +├── thezoker/ # Community namespace +│ └── modules/ +│ └── nodejs/ +└── whizus/ # Community namespace + └── modules/ + ├── exoscale-zone/ + └── ... +``` + +#### Creating Your Own Namespace + +To contribute modules under your own namespace: + +1. Fork the registry repository +2. Create a directory with your GitHub username or organization name under `/registry/` +3. Add a `modules/` subdirectory for your modules +4. Each module gets its own directory containing the standard module files +5. Submit a pull request to have your modules included in the registry + +#### Namespace Verification + +- Modules in the `coder` namespace are automatically verified +- Verification status is indicated in the module's README frontmatter + +--- + +## Module Implementation Patterns + +Common patterns for building reliable Coder modules. + +### Core Implementation Patterns + +#### The Standard Module Structure + +Standard structure for Coder modules: + +```tf +terraform { + required_version = ">= 1.0" + required_providers { + coder = { + source = "coder/coder" + version = ">= 2.5" + } + } +} + +# Required variables +variable "agent_id" { + type = string + description = "The ID of a Coder agent." +} + +# Optional variables with sensible defaults +variable "version" { + type = string + description = "Version to install." + default = "latest" +} + +# Data sources for workspace context +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} + +# Main functionality +resource "coder_script" "install" { + agent_id = var.agent_id + script = templatefile("${path.module}/install.sh", { + version = var.version + }) +} + +resource "coder_app" "main" { + count = data.coder_workspace.me.start_count + agent_id = var.agent_id + display_name = "Application" + icon = "/icon/app.svg" + url = "http://localhost:8080" +} + +# Outputs for other modules or templates +output "app_url" { + value = try(coder_app.main[0].url, "") + description = "The URL to access the application." +} +``` + +#### Conditional Resource Creation + +Modules use conditional resource creation based on configuration variables: + +```tf +# Example from jfrog-oauth module +resource "coder_env" "jfrog_ide_url" { + count = var.configure_code_server ? 1 : 0 + agent_id = var.agent_id + name = "JFROG_IDE_URL" + value = var.jfrog_url +} + +resource "coder_env" "goproxy" { + count = length(var.package_managers.go) == 0 ? 0 : 1 + agent_id = var.agent_id + name = "GOPROXY" + value = join(",", [ + for repo in var.package_managers.go : + "https://${local.username}:${data.coder_external_auth.jfrog.access_token}@${local.jfrog_host}/artifactory/api/go/${repo}" + ]) +} + +# Example from vscode-desktop module +resource "coder_app" "vscode" { + agent_id = var.agent_id + external = true + icon = "/icon/code.svg" + slug = "vscode" + display_name = "VS Code Desktop" + order = var.order + group = var.group + + url = join("", [ + "vscode://coder.coder-remote/open", + "?owner=", + data.coder_workspace_owner.me.name, + "&workspace=", + data.coder_workspace.me.name, + var.folder != "" ? join("", ["&folder=", var.folder]) : "", + var.open_recent ? "&openRecent" : "", + "&url=", + data.coder_workspace.me.access_url, + "&token=$SESSION_TOKEN", + ]) +} +``` + +### Advanced Implementation Patterns + +#### Complex Installation Pattern + +Complex modules handle multi-step installation within a single script: + +```tf +resource "coder_script" "install" { + agent_id = var.agent_id + display_name = "Install Application" + script = templatefile("${path.module}/install.sh", { + version = var.version + arch = data.coder_provisioner.me.arch + workspace_id = data.coder_workspace.me.id + folder = var.folder + }) + run_on_start = true +} +``` + +#### Service Installation Pattern + +Modules typically install and configure services during workspace startup: + +```tf +resource "coder_script" "install_service" { + agent_id = var.agent_id + display_name = "Install Service" + script = file("${path.module}/install.sh") + run_on_start = true +} + +resource "coder_app" "service" { + agent_id = var.agent_id + slug = "service" + display_name = "Service" + url = "http://localhost:${var.port}" + icon = "/icon/service.svg" + subdomain = true + + healthcheck { + url = "http://localhost:${var.port}/health" + interval = 5 + threshold = 6 + } +} +``` + +#### Configuration Template Pattern + +Modules often need to generate configuration files: + +```tf +resource "coder_script" "generate_config" { + agent_id = var.agent_id + script = templatefile("${path.module}/generate-config.sh", { + config_content = templatefile("${path.module}/config.tpl", { + workspace_name = data.coder_workspace.me.name + owner_email = data.coder_workspace_owner.me.email + custom_settings = var.custom_settings + }) + }) +} +``` + +#### External Service Integration Pattern + +Modules that integrate with external services use external authentication: + +```tf +variable "external_auth_id" { + type = string + description = "External auth ID for the service." + default = "github" +} + +data "coder_external_auth" "service" { + id = var.external_auth_id +} + +resource "coder_script" "setup_integration" { + agent_id = var.agent_id + script = templatefile("${path.module}/setup.sh", { + auth_token = data.coder_external_auth.service.access_token + service_url = var.service_url + external_id = data.coder_external_auth.service.id + }) + run_on_start = true +} +``` + +### User Experience Patterns + +#### Simple Configuration + +Modules provide sensible defaults to minimize required configuration: + +```tf +variable "port" { + type = number + description = "Port number for the application." + default = 8080 +} + +variable "extensions" { + type = list(string) + description = "List of extensions to install." + default = [] +} + +# Most configuration is optional with good defaults +resource "coder_app" "main" { + agent_id = var.agent_id + slug = "app" + display_name = "Application" + url = "http://localhost:${var.port}" + icon = "/icon/app.svg" + subdomain = true +} +``` + +#### Accepting Template Parameters + +Modules typically receive user preferences through variables passed from templates: + +```tf +variable "theme" { + type = string + description = "UI theme to use." + default = "dark" + validation { + condition = contains(["dark", "light", "auto"], var.theme) + error_message = "Theme must be dark, light, or auto." + } +} + +variable "extensions" { + type = list(string) + description = "List of extensions to install." + default = ["git", "docker"] +} + +# Use the template-provided values +resource "coder_script" "configure_theme" { + agent_id = var.agent_id + script = "configure_app --theme=${var.theme}" +} +``` + +Templates handle user input, modules handle implementation. + +### Error Handling and Resilience Patterns + +#### Application Health Checks + +Modules typically include health checks for web applications: + +```tf +# Example from vscode-web module +resource "coder_app" "vscode-web" { + agent_id = var.agent_id + slug = var.slug + display_name = var.display_name + url = local.url + icon = "/icon/code.svg" + subdomain = var.subdomain + share = var.share + order = var.order + group = var.group + + healthcheck { + url = local.healthcheck_url + interval = 5 + threshold = 6 + } +} + +# Example from windows-rdp module +resource "coder_app" "windows-rdp" { + agent_id = var.agent_id + share = var.share + slug = "web-rdp" + display_name = "Web RDP" + url = "http://localhost:7171" + icon = "/icon/desktop.svg" + subdomain = true + order = var.order + group = var.group + + healthcheck { + url = "http://localhost:7171" + interval = 5 + threshold = 15 + } +} +``` + +--- + +## Reference & Resources + +Quick reference for module development and usage. + +### Quick Reference Guide + +#### Essential Module Structure + +Every Coder module follows this basic structure: + +``` +module-name/ +├── main.tf # Core Terraform configuration +├── README.md # Documentation with frontmatter +├── main.test.ts # Test suite (required) +├── run.sh # Optional execution script +└── assets/ # Optional additional files + ├── scripts/ + ├── configs/ + └── templates/ +``` + +#### Required Frontmatter Format + +All module READMEs must include proper frontmatter: + +```yaml +--- +display_name: Module Display Name +description: Brief description of module functionality +icon: ../../../../.icons/module-icon.svg +maintainer_github: github-username +verified: false # true for official modules +tags: [tag1, tag2, tag3] +--- +``` + +#### Common Module Patterns + +**Basic Module Template:** + +```tf +terraform { + required_version = ">= 1.0" + required_providers { + coder = { + source = "coder/coder" + version = ">= 2.5" + } + } +} + +variable "agent_id" { + type = string + description = "The ID of a Coder agent." +} + +variable "port" { + type = number + description = "The port to run JupyterLab on." + default = 19999 +} + +variable "share" { + type = string + default = "owner" + validation { + condition = var.share == "owner" || var.share == "authenticated" || var.share == "public" + error_message = "Incorrect value. Please set either 'owner', 'authenticated', or 'public'." + } +} + +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} + +resource "coder_script" "jupyterlab" { + agent_id = var.agent_id + display_name = "jupyterlab" + icon = "/icon/jupyter.svg" + script = templatefile("${path.module}/run.sh", { + LOG_PATH : var.log_path, + PORT : var.port + BASE_URL : var.subdomain ? "" : "/@${data.coder_workspace_owner.me.name}/${data.coder_workspace.me.name}/apps/jupyterlab" + }) + run_on_start = true +} + +resource "coder_app" "jupyterlab" { + agent_id = var.agent_id + slug = "jupyterlab" + display_name = "JupyterLab" + url = var.subdomain ? "http://localhost:${var.port}" : "http://localhost:${var.port}/@${data.coder_workspace_owner.me.name}/${data.coder_workspace.me.name}/apps/jupyterlab" + icon = "/icon/jupyter.svg" + subdomain = var.subdomain + share = var.share + order = var.order + group = var.group +} +``` + +### Development Resources + +#### Testing Framework + +The registry uses Bun for testing with specific patterns: + +```typescript +import { describe, expect, it } from "bun:test"; +import { + runTerraformApply, + runTerraformInit, + testRequiredVariables, +} from "~test"; + +describe("module-name", async () => { + await runTerraformInit(import.meta.dir); + + testRequiredVariables(import.meta.dir, { + agent_id: "test-agent", + }); + + it("creates expected resources", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "test-agent", + }); + + // Test assertions + expect(state.resources).toBeDefined(); + }); +}); +``` + +#### Validation Tools + +Use the built-in validation tools: + +```bash +# Format code +bun run fmt + +# Validate Terraform +bun run terraform-validate + +# Run tests +bun test + +# Test specific module +bun test -t "module-name" +``` + +#### Documentation Standards + +Documentation requirements: + +- Write clear descriptions and examples +- Use consistent formatting +- Keep docs updated with code changes +- Explain why, not just what + +### Module Registry Resources + +#### Official Registry + +- **Registry Website**: [https://registry.coder.com](https://registry.coder.com) +- **GitHub Repository**: [https://github.com/coder/registry](https://github.com/coder/registry) +- **Contributing Guide**: [CONTRIBUTING.md](https://github.com/coder/registry/blob/main/CONTRIBUTING.md) + +#### Coder Documentation + +- **Terraform Modules Guide**: [https://coder.com/docs/admin/templates/extending-templates/modules](https://coder.com/docs/admin/templates/extending-templates/modules) +- **Template Creation**: [https://coder.com/docs/tutorials/template-from-scratch](https://coder.com/docs/tutorials/template-from-scratch) +- **Coder Provider Documentation**: Available in the Terraform Registry + +#### Community Resources + +- **Discord Community**: [https://discord.gg/coder](https://discord.gg/coder) +- **GitHub Discussions**: Repository discussions for questions and ideas +- **Community Examples**: Template examples in the main Coder repository + +### Module Versioning + +#### Semantic Versioning + +Modules follow semantic versioning (MAJOR.MINOR.PATCH): + +- **Patch** (1.2.3 → 1.2.4): Bug fixes that don't affect the API +- **Minor** (1.2.3 → 1.3.0): New features, backward-compatible changes +- **Major** (1.2.3 → 2.0.0): Breaking changes (removing inputs, changing types) + +#### Version Bumping + +When modifying a module, use the version bump script: + +```bash +# For bug fixes +./.github/scripts/version-bump.sh patch + +# For new features +./.github/scripts/version-bump.sh minor + +# For breaking changes +./.github/scripts/version-bump.sh major +``` + +The script automatically detects changed modules, calculates new version numbers, and updates README files. + +--- + +_This document represents the current state of Coder modules and will continue to evolve as the ecosystem grows. For the most up-to-date information, always refer to the official Coder documentation and registry._ + +**Version**: 1.0 +**Last Updated**: June 2025 +**Maintained by**: Coder Team \ No newline at end of file