- 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
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 Terraformvariableblocks 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;latestis 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
- Create
modules/<module-id>/module.jsonfollowing the schema above. - Run
go build ./coderd/templatebuilder/to verify the embed compiles. - 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.