mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add base template variables to API (#26425)
This commit is contained in:
Generated
+18
@@ -23621,6 +23621,12 @@ const docTemplate = `{
|
||||
},
|
||||
"os": {
|
||||
"type": "string"
|
||||
},
|
||||
"variables": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.TemplateBuilderModuleVariable"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -23655,6 +23661,12 @@ const docTemplate = `{
|
||||
"base_template_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"base_variable_values": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"modules": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@@ -23684,6 +23696,12 @@ const docTemplate = `{
|
||||
"base_template_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"base_variable_values": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
Generated
+18
@@ -21671,6 +21671,12 @@
|
||||
},
|
||||
"os": {
|
||||
"type": "string"
|
||||
},
|
||||
"variables": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.TemplateBuilderModuleVariable"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -21705,6 +21711,12 @@
|
||||
"base_template_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"base_variable_values": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"modules": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@@ -21731,6 +21743,12 @@
|
||||
"base_template_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"base_variable_values": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -41,6 +41,7 @@ type BaseManifest struct {
|
||||
DisplayName string `json:"display_name"`
|
||||
OS string `json:"os"`
|
||||
DefaultContext BaseDefaultContext `json:"default_context"`
|
||||
Variables []ModuleVariable `json:"variables"`
|
||||
}
|
||||
|
||||
// BaseDefaultContext holds default render values stored in base.json.
|
||||
@@ -192,6 +193,17 @@ func BaseTemplateIDs() []string {
|
||||
return ids
|
||||
}
|
||||
|
||||
// BaseVariables returns the user-facing variables for a given base
|
||||
// template ID. Computed variables are excluded. Returns nil if the
|
||||
// base is unknown or has no variables.
|
||||
func BaseVariables(exampleID string) []ModuleVariable {
|
||||
bases, err := loadBases()
|
||||
if err != nil || bases[exampleID] == nil {
|
||||
return nil
|
||||
}
|
||||
return bases[exampleID].Manifest.Variables
|
||||
}
|
||||
|
||||
// BaseTemplateFS returns a filesystem rooted at the given base template
|
||||
// directory within the embedded bases catalog. Returns an error if
|
||||
// exampleID is not a known base template.
|
||||
|
||||
@@ -4,5 +4,24 @@
|
||||
"os": "linux",
|
||||
"default_context": {
|
||||
"container_image": "codercom/enterprise-base:ubuntu"
|
||||
}
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"name": "use_kubeconfig",
|
||||
"type": "bool",
|
||||
"description": "Use host kubeconfig. Set to true if the Coder host is running outside the Kubernetes cluster for workspaces.",
|
||||
"default": false,
|
||||
"required": false,
|
||||
"sensitive": false,
|
||||
"computed": false
|
||||
},
|
||||
{
|
||||
"name": "namespace",
|
||||
"type": "string",
|
||||
"description": "The Kubernetes namespace to create workspaces in. Must exist prior to creating workspaces.",
|
||||
"required": true,
|
||||
"sensitive": false,
|
||||
"computed": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"maps"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -21,6 +22,9 @@ var numberPattern = regexp.MustCompile(`^-?[0-9]+(\.[0-9]+)?$`)
|
||||
// ComposeRequest describes which base template and modules to render.
|
||||
type ComposeRequest struct {
|
||||
BaseTemplateID string
|
||||
// BaseVariableValues maps base template variable names to their
|
||||
// user-supplied values.
|
||||
BaseVariableValues map[string]string
|
||||
// RegistryURL is the module registry base URL from the deployment
|
||||
// config (CODER_TEMPLATE_BUILDER_REGISTRY_URL).
|
||||
RegistryURL string
|
||||
@@ -49,7 +53,7 @@ type ComposeResult struct {
|
||||
// source files. It extracts the coder_agent resource name from the
|
||||
// rendered base HCL and wires it into each module block.
|
||||
func Compose(req ComposeRequest) (*ComposeResult, error) {
|
||||
mainTF, err := renderBase(req.BaseTemplateID)
|
||||
mainTF, err := renderBase(req.BaseTemplateID, req.BaseVariableValues)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -93,9 +97,14 @@ func formatHCL(src []byte) []byte {
|
||||
return hclwrite.Format(src)
|
||||
}
|
||||
|
||||
// renderBase renders the base template for the given example ID.
|
||||
func renderBase(baseTemplateID string) ([]byte, error) {
|
||||
// renderBase renders the base template for the given example ID,
|
||||
// merging any user-supplied variable values into the render context.
|
||||
func renderBase(baseTemplateID string, baseVars map[string]string) ([]byte, error) {
|
||||
renderCtx := DefaultBaseRenderContext(baseTemplateID)
|
||||
if renderCtx.Variables == nil {
|
||||
renderCtx.Variables = make(map[string]string)
|
||||
}
|
||||
maps.Copy(renderCtx.Variables, baseVars)
|
||||
mainTF, err := RenderBaseTemplate(baseTemplateID, "main.tf.tmpl", renderCtx)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("render base template: %w", err)
|
||||
|
||||
@@ -72,12 +72,14 @@ func (api *API) templateBuilderBases(rw http.ResponseWriter, r *http.Request) {
|
||||
slog.F("base_template_id", id))
|
||||
continue
|
||||
}
|
||||
vars := baseVariablesToSDK(templatebuilder.BaseVariables(id))
|
||||
bases = append(bases, codersdk.TemplateBuilderBase{
|
||||
ID: ex.ID,
|
||||
Name: ex.Name,
|
||||
Description: ex.Description,
|
||||
Icon: ex.Icon,
|
||||
OS: string(templatebuilder.BaseTemplateOS(id)),
|
||||
Variables: vars,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -90,6 +92,26 @@ func (api *API) templateBuilderBases(rw http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// baseVariablesToSDK converts base template variables to the SDK type,
|
||||
// filtering out computed variables that the builder wires automatically.
|
||||
func baseVariablesToSDK(vars []templatebuilder.ModuleVariable) []codersdk.TemplateBuilderModuleVariable {
|
||||
out := make([]codersdk.TemplateBuilderModuleVariable, 0, len(vars))
|
||||
for _, v := range vars {
|
||||
if v.Computed {
|
||||
continue
|
||||
}
|
||||
out = append(out, codersdk.TemplateBuilderModuleVariable{
|
||||
Name: v.Name,
|
||||
Type: codersdk.TemplateBuilderVariableType(v.Type),
|
||||
Description: v.Description,
|
||||
Default: v.Default,
|
||||
Required: v.Required,
|
||||
Sensitive: v.Sensitive,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// @Summary List template builder modules
|
||||
// @ID list-template-builder-modules
|
||||
// @Security CoderSessionToken
|
||||
@@ -171,8 +193,9 @@ func (api *API) templateBuilderCompose(rw http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
composeReq := templatebuilder.ComposeRequest{
|
||||
BaseTemplateID: req.BaseTemplateID,
|
||||
RegistryURL: api.DeploymentValues.TemplateBuilder.RegistryURL.String(),
|
||||
BaseTemplateID: req.BaseTemplateID,
|
||||
BaseVariableValues: req.BaseVariableValues,
|
||||
RegistryURL: api.DeploymentValues.TemplateBuilder.RegistryURL.String(),
|
||||
}
|
||||
for _, m := range req.Modules {
|
||||
composeReq.Modules = append(composeReq.Modules, templatebuilder.ComposeModule{
|
||||
@@ -280,8 +303,9 @@ func (api *API) templateBuilderCreateTemplate(rw http.ResponseWriter, r *http.Re
|
||||
|
||||
// Compose the template.
|
||||
composeReq := templatebuilder.ComposeRequest{
|
||||
BaseTemplateID: req.BaseTemplateID,
|
||||
RegistryURL: api.DeploymentValues.TemplateBuilder.RegistryURL.String(),
|
||||
BaseTemplateID: req.BaseTemplateID,
|
||||
BaseVariableValues: req.BaseVariableValues,
|
||||
RegistryURL: api.DeploymentValues.TemplateBuilder.RegistryURL.String(),
|
||||
}
|
||||
for _, m := range req.Modules {
|
||||
composeReq.Modules = append(composeReq.Modules, templatebuilder.ComposeModule{
|
||||
|
||||
@@ -34,12 +34,53 @@ func TestTemplateBuilderBases(t *testing.T) {
|
||||
basesByID[b.ID] = b
|
||||
}
|
||||
|
||||
for _, id := range templatebuilder.BaseTemplateIDs() {
|
||||
b, ok := basesByID[id]
|
||||
require.True(t, ok, "base %q missing from response", id)
|
||||
require.NotEmpty(t, b.Name)
|
||||
require.NotEmpty(t, b.Icon)
|
||||
require.Equal(t, string(templatebuilder.BaseTemplateOS(id)), b.OS)
|
||||
type baseSpec struct {
|
||||
id string
|
||||
expectedOS string
|
||||
expectedVars []string
|
||||
hasVariables bool
|
||||
}
|
||||
|
||||
specs := []baseSpec{
|
||||
{
|
||||
id: "docker",
|
||||
expectedOS: "linux",
|
||||
hasVariables: false,
|
||||
},
|
||||
{
|
||||
id: "kubernetes",
|
||||
expectedOS: "linux",
|
||||
hasVariables: true,
|
||||
expectedVars: []string{"namespace", "use_kubeconfig"},
|
||||
},
|
||||
{
|
||||
id: "aws-linux",
|
||||
expectedOS: "linux",
|
||||
hasVariables: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, spec := range specs {
|
||||
b, ok := basesByID[spec.id]
|
||||
require.True(t, ok, "base %q missing from response", spec.id)
|
||||
require.NotEmpty(t, b.Name, "base %q should have a name", spec.id)
|
||||
require.NotEmpty(t, b.Icon, "base %q should have an icon", spec.id)
|
||||
require.Equal(t, spec.expectedOS, b.OS, "base %q OS mismatch", spec.id)
|
||||
require.NotNil(t, b.Variables, "base %q should have non-nil variables slice", spec.id)
|
||||
|
||||
if spec.hasVariables {
|
||||
require.NotEmpty(t, b.Variables, "base %q should have variables", spec.id)
|
||||
varNames := make(map[string]bool, len(b.Variables))
|
||||
for _, v := range b.Variables {
|
||||
varNames[v.Name] = true
|
||||
}
|
||||
for _, expected := range spec.expectedVars {
|
||||
require.True(t, varNames[expected],
|
||||
"base %q should have variable %q", spec.id, expected)
|
||||
}
|
||||
} else {
|
||||
require.Empty(t, b.Variables, "base %q should have no variables", spec.id)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user