Files
coder/coderd/templatebuilder
Jeremy Ruppel cc381e17e8 refactor(coderd/templatebuilder): address review feedback
- Replace stub module with real code-server module manifest
- Export ParseModulesFromFS(fs.FS) for test isolation (no sync.Once coupling)
- Use sync.OnceValues for the cached loader
- Validate module ID (non-empty, unique), pinned_version, and variable types
  during parsing; reject unknown types at load time
- Normalize nil slices to empty in ToSDK() to prevent null in JSON responses
- Distinguish fs.ErrNotExist from other ReadFile errors
- Improve error messages to describe what failed, not internal plumbing
- Rewrite tests with fstest.MapFS fixtures covering all variable types,
  default pointer, validation errors, and full SDK field assertions
- Trim zero-value godoc comments
- Add future tense to README for unimplemented behavior; link RFC
2026-06-02 13:44:10 +00:00
..

templatebuilder

Package templatebuilder implements the bundled module catalog for the guided template creation workflow (RFC). It embeds module metadata (module.json manifests) into the Coder binary via go:embed and provides functions to load and convert them for the API layer.

Directory layout

coderd/templatebuilder/
    catalog.go              # embed.FS, manifest types, LoadModules(), ToSDK()
    catalog_test.go
    modules/
        <module-id>/
            module.json     # module manifest (on-disk schema)

Each subdirectory under modules/ represents a single catalog entry. The directory must contain a module.json file. Directories without one are silently skipped.

module.json schema

{
  "id": "code-server",                    // unique module identifier
  "display_name": "code-server",          // human-readable name
  "description": "VS Code in the browser",
  "icon": "...",                           // path or URL to icon asset
  "category": "IDE",                       // grouping in the UI
  "tags": ["ide", "web"],                  // internal tags (not exposed in API)
  "compatible_os": ["linux"],              // OS filter against base template
  "conflicts_with": [],                    // module IDs that conflict
  "pinned_version": "1.2.3",              // exact version bundled with this release
  "variables": [
    {
      "name": "agent_id",
      "type": "string",                    // "string" | "number" | "bool"
      "description": "The Coder agent ID.",
      "required": true,
      "sensitive": false,
      "builder_managed": true              // wired automatically, hidden from users
    },
    {
      "name": "port",
      "type": "number",
      "description": "Port to run code-server on.",
      "default": "13337",
      "required": false,
      "sensitive": false,
      "builder_managed": false
    }
  ]
}

Key fields:

  • builder_managed: Variables the compose engine will inject automatically (e.g. agent_id). These should not be shown to users.
  • sensitive: Variables containing secrets. The builder will not collect these; they will become bare Terraform variable blocks so values are supplied at a later stage.
  • pinned_version: The exact module version shipped with this Coder release. The compose endpoint will always emit this version; latest is never used.
  • compatible_os: Will be matched against the base template's OS to filter incompatible modules.
  • conflicts_with: Module IDs that should not be selected together. The UI will surface a warning but not block selection.

Two type layers

Type Location Purpose
ModuleManifest / ModuleVariable catalog.go On-disk module.json schema. Has pinned_version, tags.
codersdk.TemplateBuilderModule / codersdk.TemplateBuilderModuleVariable codersdk/templatebuilder.go API response type. Has version (mapped from pinned_version). No tags.

ModuleManifest.ToSDK() handles the conversion.

Adding a module

  1. Create modules/<module-id>/module.json following the schema above.
  2. Run go build ./coderd/templatebuilder/ to verify the embed compiles.
  3. Run go test ./coderd/templatebuilder/ to verify parsing and validation.

The catalog is bundled at build time. New modules or version bumps require a Coder release to appear in the builder.

Testing

ParseModulesFromFS(fs.FS) accepts an arbitrary filesystem, so tests can supply custom fixtures via fstest.MapFS without modifying the embedded production catalog.