mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: render base variables into templates instead of tfvars (DEVEX-287) (#26436)
Part of the Template Builder wizard PR stack.
## Problem
The kubernetes base template used Terraform `variable` blocks and
`var.*` references for `use_kubeconfig` and `namespace`, but the
composed tar bundle never included a `.tfvars` file. This caused
`terraform plan` to fail with "required template variables need values:
namespace".
## Fix
Base templates now use Go template variables (`{{ .Variables.* }}`) just
like module templates do. Values are validated, HCL-quoted, and rendered
directly into the output HCL.
Also adds explicit "variable is required" validation to both
`mergeBaseVariables` and `mergeModuleVariables`, replacing the previous
reliance on `missingkey=error` at render time for clearer error
messages.
---
> [!NOTE]
> Generated by Coder Agents on behalf of @jeremyruppel
This commit is contained in:
@@ -104,7 +104,13 @@ func renderBase(baseTemplateID string, baseVars map[string]string) ([]byte, erro
|
||||
if renderCtx.Variables == nil {
|
||||
renderCtx.Variables = make(map[string]string)
|
||||
}
|
||||
maps.Copy(renderCtx.Variables, baseVars)
|
||||
|
||||
vars, err := mergeBaseVariables(baseTemplateID, baseVars)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("base %q: %w", baseTemplateID, err)
|
||||
}
|
||||
maps.Copy(renderCtx.Variables, vars)
|
||||
|
||||
mainTF, err := RenderBaseTemplate(baseTemplateID, "main.tf.tmpl", renderCtx)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("render base template: %w", err)
|
||||
@@ -112,6 +118,63 @@ func renderBase(baseTemplateID string, baseVars map[string]string) ([]byte, erro
|
||||
return mainTF, nil
|
||||
}
|
||||
|
||||
// mergeBaseVariables builds the final Variables map for a base template.
|
||||
// It starts with manifest defaults, overlays caller-supplied values,
|
||||
// validates types, and converts to HCL literals.
|
||||
func mergeBaseVariables(baseTemplateID string, callerVars map[string]string) (map[string]string, error) {
|
||||
allVars := BaseVariables(baseTemplateID)
|
||||
if len(allVars) == 0 && len(callerVars) == 0 {
|
||||
return make(map[string]string), nil
|
||||
}
|
||||
|
||||
allowedVars := make(map[string]ModuleVariable, len(allVars))
|
||||
for _, v := range allVars {
|
||||
if v.Computed || v.Sensitive {
|
||||
continue
|
||||
}
|
||||
allowedVars[v.Name] = v
|
||||
}
|
||||
|
||||
// Validate caller-supplied keys and values.
|
||||
for k, val := range callerVars {
|
||||
v, ok := allowedVars[k]
|
||||
if !ok {
|
||||
return nil, xerrors.Errorf("unknown variable %q", k)
|
||||
}
|
||||
if err := validateVariableValue(v, val); err != nil {
|
||||
return nil, xerrors.Errorf("variable %q: %w", k, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Build merged map from manifest defaults.
|
||||
merged := make(map[string]string, len(allVars))
|
||||
for _, v := range allVars {
|
||||
if v.Computed || v.Sensitive {
|
||||
continue
|
||||
}
|
||||
if len(v.Default) > 0 && isSimpleJSONValue(v.Default) {
|
||||
merged[v.Name] = string(v.Default)
|
||||
}
|
||||
}
|
||||
|
||||
// Overlay validated caller values, converting to HCL literals.
|
||||
for k, val := range callerVars {
|
||||
merged[k] = toHCLLiteral(allowedVars[k], val)
|
||||
}
|
||||
|
||||
// Ensure all required variables without defaults have a value.
|
||||
for _, v := range allVars {
|
||||
if v.Computed || v.Sensitive {
|
||||
continue
|
||||
}
|
||||
if v.Required && merged[v.Name] == "" {
|
||||
return nil, xerrors.Errorf("variable %q is required", v.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return merged, nil
|
||||
}
|
||||
|
||||
// loadCatalogMap loads the module catalog and returns it as a map keyed
|
||||
// by module ID.
|
||||
func loadCatalogMap() (map[string]ModuleManifest, error) {
|
||||
@@ -248,6 +311,17 @@ func mergeModuleVariables(manifest ModuleManifest, callerVars map[string]string)
|
||||
for k, val := range callerVars {
|
||||
merged[k] = toHCLLiteral(allowedVars[k], val)
|
||||
}
|
||||
|
||||
// Ensure all required variables without defaults have a value.
|
||||
for _, v := range manifest.Variables {
|
||||
if v.Computed || v.Sensitive {
|
||||
continue
|
||||
}
|
||||
if v.Required && merged[v.Name] == "" {
|
||||
return nil, xerrors.Errorf("variable %q is required", v.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return merged, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user