mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
feat: include template variables in dynamic parameter rendering (#18819)
Closes https://github.com/coder/coder/issues/18671 Template variables now loaded into dynamic parameters.
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/apiversion"
|
||||
@@ -41,9 +42,10 @@ type loader struct {
|
||||
templateVersionID uuid.UUID
|
||||
|
||||
// cache of objects
|
||||
templateVersion *database.TemplateVersion
|
||||
job *database.ProvisionerJob
|
||||
terraformValues *database.TemplateVersionTerraformValue
|
||||
templateVersion *database.TemplateVersion
|
||||
job *database.ProvisionerJob
|
||||
terraformValues *database.TemplateVersionTerraformValue
|
||||
templateVariableValues *[]database.TemplateVersionVariable
|
||||
}
|
||||
|
||||
// Prepare is the entrypoint for this package. It loads the necessary objects &
|
||||
@@ -61,6 +63,12 @@ func Prepare(ctx context.Context, db database.Store, cache files.FileAcquirer, v
|
||||
return l.Renderer(ctx, db, cache)
|
||||
}
|
||||
|
||||
func WithTemplateVariableValues(vals []database.TemplateVersionVariable) func(r *loader) {
|
||||
return func(r *loader) {
|
||||
r.templateVariableValues = &vals
|
||||
}
|
||||
}
|
||||
|
||||
func WithTemplateVersion(tv database.TemplateVersion) func(r *loader) {
|
||||
return func(r *loader) {
|
||||
if tv.ID == r.templateVersionID {
|
||||
@@ -127,6 +135,14 @@ func (r *loader) loadData(ctx context.Context, db database.Store) error {
|
||||
r.terraformValues = &values
|
||||
}
|
||||
|
||||
if r.templateVariableValues == nil {
|
||||
vals, err := db.GetTemplateVersionVariables(ctx, r.templateVersion.ID)
|
||||
if err != nil && !xerrors.Is(err, sql.ErrNoRows) {
|
||||
return xerrors.Errorf("template version variables: %w", err)
|
||||
}
|
||||
r.templateVariableValues = &vals
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -160,13 +176,17 @@ func (r *loader) dynamicRenderer(ctx context.Context, db database.Store, cache *
|
||||
}
|
||||
}()
|
||||
|
||||
tfVarValues, err := VariableValues(*r.templateVariableValues)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("parse variable values: %w", err)
|
||||
}
|
||||
|
||||
// If they can read the template version, then they can read the file for
|
||||
// parameter loading purposes.
|
||||
//nolint:gocritic
|
||||
fileCtx := dbauthz.AsFileReader(ctx)
|
||||
|
||||
var templateFS fs.FS
|
||||
var err error
|
||||
|
||||
templateFS, err = cache.Acquire(fileCtx, db, r.job.FileID)
|
||||
if err != nil {
|
||||
@@ -189,6 +209,7 @@ func (r *loader) dynamicRenderer(ctx context.Context, db database.Store, cache *
|
||||
db: db,
|
||||
ownerErrors: make(map[uuid.UUID]error),
|
||||
close: cache.Close,
|
||||
tfvarValues: tfVarValues,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -199,6 +220,7 @@ type dynamicRenderer struct {
|
||||
|
||||
ownerErrors map[uuid.UUID]error
|
||||
currentOwner *previewtypes.WorkspaceOwner
|
||||
tfvarValues map[string]cty.Value
|
||||
|
||||
once sync.Once
|
||||
close func()
|
||||
@@ -229,6 +251,7 @@ func (r *dynamicRenderer) Render(ctx context.Context, ownerID uuid.UUID, values
|
||||
PlanJSON: r.data.terraformValues.CachedPlan,
|
||||
ParameterValues: values,
|
||||
Owner: *r.currentOwner,
|
||||
TFVars: r.tfvarValues,
|
||||
// Do not emit parser logs to coderd output logs.
|
||||
// TODO: Returning this logs in the output would benefit the caller.
|
||||
// Unsure how large the logs can be, so for now we just discard them.
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package dynamicparameters
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/json"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
)
|
||||
|
||||
// VariableValues is a helper function that converts a slice of TemplateVersionVariable
|
||||
// into a map of cty.Value for use in coder/preview.
|
||||
func VariableValues(vals []database.TemplateVersionVariable) (map[string]cty.Value, error) {
|
||||
ctyVals := make(map[string]cty.Value, len(vals))
|
||||
for _, v := range vals {
|
||||
value := v.Value
|
||||
if value == "" && v.DefaultValue != "" {
|
||||
value = v.DefaultValue
|
||||
}
|
||||
|
||||
if value == "" {
|
||||
// Empty strings are unsupported I guess?
|
||||
continue // omit non-set vals
|
||||
}
|
||||
|
||||
var err error
|
||||
switch v.Type {
|
||||
// Defaulting the empty type to "string"
|
||||
// TODO: This does not match the terraform behavior, however it is too late
|
||||
// at this point in the code to determine this, as the database type stores all values
|
||||
// as strings. The code needs to be fixed in the `Parse` step of the provisioner.
|
||||
// That step should determine the type of the variable correctly and store it in the database.
|
||||
case "string", "":
|
||||
ctyVals[v.Name] = cty.StringVal(value)
|
||||
case "number":
|
||||
ctyVals[v.Name], err = cty.ParseNumberVal(value)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("parse variable %q: %w", v.Name, err)
|
||||
}
|
||||
case "bool":
|
||||
parsed, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("parse variable %q: %w", v.Name, err)
|
||||
}
|
||||
ctyVals[v.Name] = cty.BoolVal(parsed)
|
||||
default:
|
||||
// If it is a complex type, let the cty json code give it a try.
|
||||
// TODO: Ideally we parse `list` & `map` and build the type ourselves.
|
||||
ty, err := json.ImpliedType([]byte(value))
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("implied type for variable %q: %w", v.Name, err)
|
||||
}
|
||||
|
||||
jv, err := json.Unmarshal([]byte(value), ty)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("unmarshal variable %q: %w", v.Name, err)
|
||||
}
|
||||
ctyVals[v.Name] = jv
|
||||
}
|
||||
}
|
||||
|
||||
return ctyVals, nil
|
||||
}
|
||||
Reference in New Issue
Block a user