From e86224c36d2024dff48c4bceaf287494dd1cae44 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 9 Jul 2025 10:56:06 -0600 Subject: [PATCH 01/14] feat: include template variables in dynamic parameter rendering --- coderd/dynamicparameters/render.go | 31 ++++++++++-- coderd/dynamicparameters/variablevalues.go | 58 ++++++++++++++++++++++ coderd/wsbuilder/wsbuilder.go | 6 +++ go.mod | 2 +- go.sum | 4 +- 5 files changed, 94 insertions(+), 7 deletions(-) create mode 100644 coderd/dynamicparameters/variablevalues.go diff --git a/coderd/dynamicparameters/render.go b/coderd/dynamicparameters/render.go index 8a5a80cd25d22..7f0a98f18ce55 100644 --- a/coderd/dynamicparameters/render.go +++ b/coderd/dynamicparameters/render.go @@ -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. diff --git a/coderd/dynamicparameters/variablevalues.go b/coderd/dynamicparameters/variablevalues.go new file mode 100644 index 0000000000000..1bb72cc2b0510 --- /dev/null +++ b/coderd/dynamicparameters/variablevalues.go @@ -0,0 +1,58 @@ +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" +) + +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 { + 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 +} diff --git a/coderd/wsbuilder/wsbuilder.go b/coderd/wsbuilder/wsbuilder.go index 90ea02e966a09..d608682c58eee 100644 --- a/coderd/wsbuilder/wsbuilder.go +++ b/coderd/wsbuilder/wsbuilder.go @@ -633,10 +633,16 @@ func (b *Builder) getDynamicParameterRenderer() (dynamicparameters.Renderer, err return nil, xerrors.Errorf("get template version terraform values: %w", err) } + variableValues, err := b.getTemplateVersionVariables() + if err != nil { + return nil, xerrors.Errorf("get template version variables: %w", err) + } + renderer, err := dynamicparameters.Prepare(b.ctx, b.store, b.fileCache, tv.ID, dynamicparameters.WithTemplateVersion(*tv), dynamicparameters.WithProvisionerJob(*job), dynamicparameters.WithTerraformValues(*tfVals), + dynamicparameters.WithTemplateVariableValues(variableValues), ) if err != nil { return nil, xerrors.Errorf("get template version renderer: %w", err) diff --git a/go.mod b/go.mod index fa91932ceaecf..4771818015991 100644 --- a/go.mod +++ b/go.mod @@ -483,7 +483,7 @@ require ( require ( github.com/coder/agentapi-sdk-go v0.0.0-20250505131810-560d1d88d225 github.com/coder/aisdk-go v0.0.9 - github.com/coder/preview v1.0.3-0.20250701142654-c3d6e86b9393 + github.com/coder/preview v1.0.3-0.20250709160236-8ddde200cd69 github.com/fsnotify/fsnotify v1.9.0 github.com/mark3labs/mcp-go v0.33.0 ) diff --git a/go.sum b/go.sum index e46a4eb61a477..a0950bf08fc64 100644 --- a/go.sum +++ b/go.sum @@ -916,8 +916,8 @@ github.com/coder/pq v1.10.5-0.20250630052411-a259f96b6102 h1:ahTJlTRmTogsubgRVGO github.com/coder/pq v1.10.5-0.20250630052411-a259f96b6102/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 h1:3A0ES21Ke+FxEM8CXx9n47SZOKOpgSE1bbJzlE4qPVs= github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0/go.mod h1:5UuS2Ts+nTToAMeOjNlnHFkPahrtDkmpydBen/3wgZc= -github.com/coder/preview v1.0.3-0.20250701142654-c3d6e86b9393 h1:l+m2liikn8JoEv6C22QIV4qseolUfvNsyUNA6JJsD6Y= -github.com/coder/preview v1.0.3-0.20250701142654-c3d6e86b9393/go.mod h1:efDWGlO/PZPrvdt5QiDhMtTUTkPxejXo9c0wmYYLLjM= +github.com/coder/preview v1.0.3-0.20250709160236-8ddde200cd69 h1:bQ3r5Y22V1heD6Ah4kN/wMJ8gflyGPhzNtiFefytBVs= +github.com/coder/preview v1.0.3-0.20250709160236-8ddde200cd69/go.mod h1:efDWGlO/PZPrvdt5QiDhMtTUTkPxejXo9c0wmYYLLjM= github.com/coder/quartz v0.2.1 h1:QgQ2Vc1+mvzewg2uD/nj8MJ9p9gE+QhGJm+Z+NGnrSE= github.com/coder/quartz v0.2.1/go.mod h1:vsiCc+AHViMKH2CQpGIpFgdHIEQsxwm8yCscqKmzbRA= github.com/coder/retry v1.5.1 h1:iWu8YnD8YqHs3XwqrqsjoBTAVqT9ml6z9ViJ2wlMiqc= From 40ee3fd63e0122855da46985ed524a8ba2779716 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 9 Jul 2025 11:25:31 -0600 Subject: [PATCH 02/14] test: add unit test for template vars in dynamic parameter --- coderd/coderdtest/dynamicparameters.go | 30 +++++++++++++++++- coderd/parameters_test.go | 32 ++++++++++++++++++++ coderd/testdata/parameters/variables/main.tf | 30 ++++++++++++++++++ 3 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 coderd/testdata/parameters/variables/main.tf diff --git a/coderd/coderdtest/dynamicparameters.go b/coderd/coderdtest/dynamicparameters.go index 28e01885560ca..c6adb6c97e786 100644 --- a/coderd/coderdtest/dynamicparameters.go +++ b/coderd/coderdtest/dynamicparameters.go @@ -29,7 +29,8 @@ type DynamicParameterTemplateParams struct { // TemplateID is used to update an existing template instead of creating a new one. TemplateID uuid.UUID - Version func(request *codersdk.CreateTemplateVersionRequest) + Version func(request *codersdk.CreateTemplateVersionRequest) + Variables []codersdk.TemplateVersionVariable } func DynamicParameterTemplate(t *testing.T, client *codersdk.Client, org uuid.UUID, args DynamicParameterTemplateParams) (codersdk.Template, codersdk.TemplateVersion) { @@ -48,6 +49,32 @@ func DynamicParameterTemplate(t *testing.T, client *codersdk.Client, org uuid.UU }, }} + userVars := make([]codersdk.VariableValue, 0, len(args.Variables)) + parseVars := make([]*proto.TemplateVariable, 0, len(args.Variables)) + for _, argv := range args.Variables { + parseVars = append(parseVars, &proto.TemplateVariable{ + Name: argv.Name, + Description: argv.Description, + Type: argv.Type, + DefaultValue: argv.DefaultValue, + Required: argv.Required, + Sensitive: argv.Sensitive, + }) + + userVars = append(userVars, codersdk.VariableValue{ + Name: argv.Name, + Value: argv.Value, + }) + } + + files.Parse = []*proto.Response{{ + Type: &proto.Response_Parse{ + Parse: &proto.ParseComplete{ + TemplateVariables: parseVars, + }, + }, + }} + mime := codersdk.ContentTypeTar if args.Zip { mime = codersdk.ContentTypeZip @@ -59,6 +86,7 @@ func DynamicParameterTemplate(t *testing.T, client *codersdk.Client, org uuid.UU if args.Version != nil { args.Version(request) } + request.UserVariableValues = userVars }) AwaitTemplateVersionJobCompleted(t, client, version.ID) diff --git a/coderd/parameters_test.go b/coderd/parameters_test.go index 855d95eb1de59..19251ff2090d0 100644 --- a/coderd/parameters_test.go +++ b/coderd/parameters_test.go @@ -343,6 +343,36 @@ func TestDynamicParametersWithTerraformValues(t *testing.T) { require.Len(t, preview.Diagnostics, 1) require.Equal(t, preview.Diagnostics[0].Extra.Code, "owner_not_found") }) + + t.Run("TemplateVariables", func(t *testing.T) { + t.Parallel() + + dynamicParametersTerraformSource, err := os.ReadFile("testdata/parameters/variables/main.tf") + require.NoError(t, err) + + setup := setupDynamicParamsTest(t, setupDynamicParamsTestParams{ + provisionerDaemonVersion: provProto.CurrentVersion.String(), + mainTF: dynamicParametersTerraformSource, + variables: []codersdk.TemplateVersionVariable{ + {Name: "one", Value: "austin", DefaultValue: "alice", Type: "string"}, + }, + plan: nil, + static: nil, + }) + + ctx := testutil.Context(t, testutil.WaitShort*100000) + stream := setup.stream + previews := stream.Chan() + + // Should see the output of the module represented + preview := testutil.RequireReceive(ctx, t, previews) + require.Equal(t, -1, preview.ID) + require.Empty(t, preview.Diagnostics) + + require.Len(t, preview.Parameters, 1) + coderdtest.AssertParameter(t, "variable_values", preview.Parameters). + Exists().Value("austin") + }) } type setupDynamicParamsTestParams struct { @@ -355,6 +385,7 @@ type setupDynamicParamsTestParams struct { static []*proto.RichParameter expectWebsocketError bool + variables []codersdk.TemplateVersionVariable } type dynamicParamsTest struct { @@ -380,6 +411,7 @@ func setupDynamicParamsTest(t *testing.T, args setupDynamicParamsTestParams) dyn Plan: args.plan, ModulesArchive: args.modulesArchive, StaticParams: args.static, + Variables: args.variables, }) ctx := testutil.Context(t, testutil.WaitShort) diff --git a/coderd/testdata/parameters/variables/main.tf b/coderd/testdata/parameters/variables/main.tf new file mode 100644 index 0000000000000..79e927f63685a --- /dev/null +++ b/coderd/testdata/parameters/variables/main.tf @@ -0,0 +1,30 @@ +// Base case for workspace tags + parameters. +terraform { + required_providers { + coder = { + source = "coder/coder" + } + docker = { + source = "kreuzwerker/docker" + version = "3.0.2" + } + } +} + +variable "one" { + default = "alice" + type = string +} + + +data "coder_parameter" "variable_values" { + name = "variable_values" + description = "Just to show the variable values" + type = "string" + default = var.one + + option { + name = "one" + value = var.one + } +} From 520fff71c273d2c7def28f69676c40716bb004fe Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 14 Jul 2025 10:14:18 -0600 Subject: [PATCH 03/14] update preview to main --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4771818015991..f2b12cbd6387c 100644 --- a/go.mod +++ b/go.mod @@ -483,7 +483,7 @@ require ( require ( github.com/coder/agentapi-sdk-go v0.0.0-20250505131810-560d1d88d225 github.com/coder/aisdk-go v0.0.9 - github.com/coder/preview v1.0.3-0.20250709160236-8ddde200cd69 + github.com/coder/preview v1.0.3-0.20250714153828-a737d4750448 github.com/fsnotify/fsnotify v1.9.0 github.com/mark3labs/mcp-go v0.33.0 ) diff --git a/go.sum b/go.sum index a0950bf08fc64..1d6ae833a5dbe 100644 --- a/go.sum +++ b/go.sum @@ -916,8 +916,8 @@ github.com/coder/pq v1.10.5-0.20250630052411-a259f96b6102 h1:ahTJlTRmTogsubgRVGO github.com/coder/pq v1.10.5-0.20250630052411-a259f96b6102/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 h1:3A0ES21Ke+FxEM8CXx9n47SZOKOpgSE1bbJzlE4qPVs= github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0/go.mod h1:5UuS2Ts+nTToAMeOjNlnHFkPahrtDkmpydBen/3wgZc= -github.com/coder/preview v1.0.3-0.20250709160236-8ddde200cd69 h1:bQ3r5Y22V1heD6Ah4kN/wMJ8gflyGPhzNtiFefytBVs= -github.com/coder/preview v1.0.3-0.20250709160236-8ddde200cd69/go.mod h1:efDWGlO/PZPrvdt5QiDhMtTUTkPxejXo9c0wmYYLLjM= +github.com/coder/preview v1.0.3-0.20250714153828-a737d4750448 h1:S86sFp4Dr4dUn++fXOMOTu6ClnEZ/NrGCYv7bxZjYYc= +github.com/coder/preview v1.0.3-0.20250714153828-a737d4750448/go.mod h1:hQtBEqOFMJ3SHl9Q9pVvDA9CpeCEXBwbONNK29+3MLk= github.com/coder/quartz v0.2.1 h1:QgQ2Vc1+mvzewg2uD/nj8MJ9p9gE+QhGJm+Z+NGnrSE= github.com/coder/quartz v0.2.1/go.mod h1:vsiCc+AHViMKH2CQpGIpFgdHIEQsxwm8yCscqKmzbRA= github.com/coder/retry v1.5.1 h1:iWu8YnD8YqHs3XwqrqsjoBTAVqT9ml6z9ViJ2wlMiqc= From a8bdd04324f6799bd2d2890c4eb5d2dfd67e0d31 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 14 Jul 2025 10:16:59 -0600 Subject: [PATCH 04/14] fmt --- coderd/testdata/parameters/variables/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/testdata/parameters/variables/main.tf b/coderd/testdata/parameters/variables/main.tf index 79e927f63685a..684ee4505abe3 100644 --- a/coderd/testdata/parameters/variables/main.tf +++ b/coderd/testdata/parameters/variables/main.tf @@ -13,7 +13,7 @@ terraform { variable "one" { default = "alice" - type = string + type = string } From a2f2de6694835cb3f439e6b091173cef0e4c1a14 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 14 Jul 2025 10:32:33 -0600 Subject: [PATCH 05/14] template version tags to respect tf vars --- coderd/templateversions.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/coderd/templateversions.go b/coderd/templateversions.go index fa5a7ed1fe757..9642b362d40f5 100644 --- a/coderd/templateversions.go +++ b/coderd/templateversions.go @@ -18,6 +18,7 @@ import ( "github.com/google/uuid" "github.com/moby/moby/pkg/namesgenerator" "github.com/sqlc-dev/pqtype" + "github.com/zclconf/go-cty/cty" "golang.org/x/xerrors" "cdr.dev/slog" @@ -1585,7 +1586,7 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht var parsedTags map[string]string var ok bool if dynamicTemplate { - parsedTags, ok = api.dynamicTemplateVersionTags(ctx, rw, organization.ID, apiKey.UserID, file) + parsedTags, ok = api.dynamicTemplateVersionTags(ctx, rw, organization.ID, apiKey.UserID, file, req.UserVariableValues) if !ok { return } @@ -1762,7 +1763,7 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht warnings)) } -func (api *API) dynamicTemplateVersionTags(ctx context.Context, rw http.ResponseWriter, orgID uuid.UUID, owner uuid.UUID, file database.File) (map[string]string, bool) { +func (api *API) dynamicTemplateVersionTags(ctx context.Context, rw http.ResponseWriter, orgID uuid.UUID, owner uuid.UUID, file database.File, templateVariables []codersdk.VariableValue) (map[string]string, bool) { ownerData, err := dynamicparameters.WorkspaceOwner(ctx, api.Database, orgID, owner) if err != nil { if httpapi.Is404Error(err) { @@ -1800,11 +1801,18 @@ func (api *API) dynamicTemplateVersionTags(ctx context.Context, rw http.Response return nil, false } + // Pass in any manually specified template variables as TFVars. + tfVarValues := make(map[string]cty.Value) + for _, variable := range templateVariables { + tfVarValues[variable.Name] = cty.StringVal(variable.Value) + } + output, diags := preview.Preview(ctx, preview.Input{ PlanJSON: nil, // Template versions are before `terraform plan` ParameterValues: nil, // No user-specified parameters Owner: *ownerData, Logger: stdslog.New(stdslog.DiscardHandler), + TFVars: tfVarValues, }, files) tagErr := dynamicparameters.CheckTags(output, diags) if tagErr != nil { From b45cbf2ada267de491593c987d3edfefbb1e4138 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 14 Jul 2025 11:51:40 -0600 Subject: [PATCH 06/14] change workspace tags test to new flow --- enterprise/coderd/workspaces_test.go | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index 1030536f2111d..8c170f7d4e207 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -2644,6 +2644,7 @@ func TestWorkspaceTagsTerraform(t *testing.T) { } } } + provider "coder" {} data "coder_workspace" "me" {} data "coder_workspace_owner" "me" {} @@ -2817,11 +2818,29 @@ func TestWorkspaceTagsTerraform(t *testing.T) { templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin()) member, memberUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) - _ = coderdenttest.NewExternalProvisionerDaemonTerraform(t, client, owner.OrganizationID, tc.provisionerTags) + // Provisioner for the empty file + emptyProvisioner := coderdenttest.NewExternalProvisionerDaemonTerraform(t, client, owner.OrganizationID, map[string]string{}) // This can take a while, so set a relatively long timeout. ctx := testutil.Context(t, 2*testutil.WaitSuperLong) + emptyTar := testutil.CreateTar(t, map[string]string{"main.tf": ""}) + emptyFi, err := templateAdmin.Upload(ctx, "application/x-tar", bytes.NewReader(emptyTar)) + emptyTv, err := templateAdmin.CreateTemplateVersion(ctx, owner.OrganizationID, codersdk.CreateTemplateVersionRequest{ + Name: testutil.GetRandomName(t), + FileID: emptyFi.ID, + StorageMethod: codersdk.ProvisionerStorageMethodFile, + Provisioner: codersdk.ProvisionerTypeTerraform, + }) + coderdtest.AwaitTemplateVersionJobCompleted(t, templateAdmin, emptyTv.ID) + tpl := coderdtest.CreateTemplate(t, templateAdmin, owner.OrganizationID, emptyTv.ID, func(request *codersdk.CreateTemplateRequest) { + request.UseClassicParameterFlow = ptr.Ref(false) + }) + _ = emptyProvisioner.Close() // No longer needed + + // The provisioner for the next template version + _ = coderdenttest.NewExternalProvisionerDaemonTerraform(t, client, owner.OrganizationID, tc.provisionerTags) + // Creating a template as a template admin must succeed templateFiles := map[string]string{"main.tf": fmt.Sprintf(mainTfTemplate, tc.tfWorkspaceTags)} tarBytes := testutil.CreateTar(t, templateFiles) @@ -2834,10 +2853,10 @@ func TestWorkspaceTagsTerraform(t *testing.T) { Provisioner: codersdk.ProvisionerTypeTerraform, ProvisionerTags: tc.createTemplateVersionRequestTags, UserVariableValues: tc.templateImportUserVariableValues, + TemplateID: tpl.ID, }) require.NoError(t, err, "failed to create template version") coderdtest.AwaitTemplateVersionJobCompleted(t, templateAdmin, tv.ID) - tpl := coderdtest.CreateTemplate(t, templateAdmin, owner.OrganizationID, tv.ID) if !tc.skipCreateWorkspace { // Creating a workspace as a non-privileged user must succeed From c0b54b1286efaeec6a83c1da4f71d67eee7e28f8 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 14 Jul 2025 12:02:44 -0600 Subject: [PATCH 07/14] linting --- enterprise/coderd/workspaces_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index 8c170f7d4e207..00546f3f82d9f 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -2826,12 +2826,16 @@ func TestWorkspaceTagsTerraform(t *testing.T) { emptyTar := testutil.CreateTar(t, map[string]string{"main.tf": ""}) emptyFi, err := templateAdmin.Upload(ctx, "application/x-tar", bytes.NewReader(emptyTar)) + require.NoError(t, err) + emptyTv, err := templateAdmin.CreateTemplateVersion(ctx, owner.OrganizationID, codersdk.CreateTemplateVersionRequest{ Name: testutil.GetRandomName(t), FileID: emptyFi.ID, StorageMethod: codersdk.ProvisionerStorageMethodFile, Provisioner: codersdk.ProvisionerTypeTerraform, }) + require.NoError(t, err) + coderdtest.AwaitTemplateVersionJobCompleted(t, templateAdmin, emptyTv.ID) tpl := coderdtest.CreateTemplate(t, templateAdmin, owner.OrganizationID, emptyTv.ID, func(request *codersdk.CreateTemplateRequest) { request.UseClassicParameterFlow = ptr.Ref(false) From d0c594aa27d1f8bc31f23960bdbaa54c41dbf553 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 17 Jul 2025 10:43:36 -0600 Subject: [PATCH 08/14] update test to use the right template version --- coderd/coderdtest/coderdtest.go | 1 + enterprise/coderd/workspaces_test.go | 10 +++------- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index 4aa968468e146..1429a85da8b27 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -1075,6 +1075,7 @@ func AwaitWorkspaceBuildJobCompleted(t testing.TB, client *codersdk.Client, buil t.Logf("waiting for workspace build job %s", build) var workspaceBuild codersdk.WorkspaceBuild require.Eventually(t, func() bool { + var err error workspaceBuild, err = client.WorkspaceBuild(ctx, build) return assert.NoError(t, err) && workspaceBuild.Job.CompletedAt != nil diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index 00546f3f82d9f..04bf8e06f4082 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -2807,7 +2807,7 @@ func TestWorkspaceTagsTerraform(t *testing.T) { client, owner := coderdenttest.New(t, &coderdenttest.Options{ Options: &coderdtest.Options{ // We intentionally do not run a built-in provisioner daemon here. - IncludeProvisionerDaemon: false, + IncludeProvisionerDaemon: true, }, LicenseOptions: &coderdenttest.LicenseOptions{ Features: license.Features{ @@ -2818,9 +2818,6 @@ func TestWorkspaceTagsTerraform(t *testing.T) { templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin()) member, memberUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) - // Provisioner for the empty file - emptyProvisioner := coderdenttest.NewExternalProvisionerDaemonTerraform(t, client, owner.OrganizationID, map[string]string{}) - // This can take a while, so set a relatively long timeout. ctx := testutil.Context(t, 2*testutil.WaitSuperLong) @@ -2832,7 +2829,7 @@ func TestWorkspaceTagsTerraform(t *testing.T) { Name: testutil.GetRandomName(t), FileID: emptyFi.ID, StorageMethod: codersdk.ProvisionerStorageMethodFile, - Provisioner: codersdk.ProvisionerTypeTerraform, + Provisioner: codersdk.ProvisionerTypeEcho, }) require.NoError(t, err) @@ -2840,7 +2837,6 @@ func TestWorkspaceTagsTerraform(t *testing.T) { tpl := coderdtest.CreateTemplate(t, templateAdmin, owner.OrganizationID, emptyTv.ID, func(request *codersdk.CreateTemplateRequest) { request.UseClassicParameterFlow = ptr.Ref(false) }) - _ = emptyProvisioner.Close() // No longer needed // The provisioner for the next template version _ = coderdenttest.NewExternalProvisionerDaemonTerraform(t, client, owner.OrganizationID, tc.provisionerTags) @@ -2865,7 +2861,7 @@ func TestWorkspaceTagsTerraform(t *testing.T) { if !tc.skipCreateWorkspace { // Creating a workspace as a non-privileged user must succeed ws, err := member.CreateUserWorkspace(ctx, memberUser.Username, codersdk.CreateWorkspaceRequest{ - TemplateID: tpl.ID, + TemplateVersionID: tv.ID, Name: coderdtest.RandomUsername(t), RichParameterValues: tc.workspaceBuildParameters, }) From 3cdcb38551f5cd9f1a3a78e86409b44d1b888148 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 17 Jul 2025 11:14:42 -0600 Subject: [PATCH 09/14] update test with some logs --- enterprise/coderd/workspaces_test.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index 04bf8e06f4082..a99186da9b36f 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -2858,14 +2858,21 @@ func TestWorkspaceTagsTerraform(t *testing.T) { require.NoError(t, err, "failed to create template version") coderdtest.AwaitTemplateVersionJobCompleted(t, templateAdmin, tv.ID) + err = templateAdmin.UpdateActiveTemplateVersion(ctx, tpl.ID, codersdk.UpdateActiveTemplateVersion{ + ID: tv.ID, + }) + require.NoError(t, err, "set to active template version") + if !tc.skipCreateWorkspace { // Creating a workspace as a non-privileged user must succeed ws, err := member.CreateUserWorkspace(ctx, memberUser.Username, codersdk.CreateWorkspaceRequest{ - TemplateVersionID: tv.ID, + TemplateID: tpl.ID, Name: coderdtest.RandomUsername(t), RichParameterValues: tc.workspaceBuildParameters, }) require.NoError(t, err, "failed to create workspace") + tagJSON, _ := json.Marshal(ws.LatestBuild.Job.Tags) + t.Logf("Created workspace build with tags: %s", tagJSON) coderdtest.AwaitWorkspaceBuildJobCompleted(t, member, ws.LatestBuild.ID) } }) From e58c5e1b8005d30e9f437b5a2ca34309a80a2ca1 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 17 Jul 2025 11:25:50 -0600 Subject: [PATCH 10/14] run dynamic and static --- enterprise/coderd/workspaces_test.go | 204 +++++++++++++++------------ 1 file changed, 113 insertions(+), 91 deletions(-) diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index a99186da9b36f..5565960dfeb4e 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -2627,6 +2627,21 @@ func TestWorkspaceTemplateParamsChange(t *testing.T) { require.Equal(t, codersdk.WorkspaceStatusDeleted, build.Status) } +type testWorkspaceTagsTerraformCase struct { + name string + // tags to apply to the external provisioner + provisionerTags map[string]string + // tags to apply to the create template version request + createTemplateVersionRequestTags map[string]string + // the coder_workspace_tags bit of main.tf. + // you can add more stuff here if you need + tfWorkspaceTags string + templateImportUserVariableValues []codersdk.VariableValue + // if we need to set parameters on workspace build + workspaceBuildParameters []codersdk.WorkspaceBuildParameter + skipCreateWorkspace bool +} + // TestWorkspaceTagsTerraform tests that a workspace can be created with tags. // This is an end-to-end-style test, meaning that we actually run the // real Terraform provisioner and validate that the workspace is created @@ -2636,7 +2651,7 @@ func TestWorkspaceTemplateParamsChange(t *testing.T) { // config file so that we only reference those // nolint:paralleltest // t.Setenv func TestWorkspaceTagsTerraform(t *testing.T) { - mainTfTemplate := ` + coderProviderTemplate := ` terraform { required_providers { coder = { @@ -2644,34 +2659,11 @@ func TestWorkspaceTagsTerraform(t *testing.T) { } } } - - provider "coder" {} - data "coder_workspace" "me" {} - data "coder_workspace_owner" "me" {} - data "coder_parameter" "unrelated" { - name = "unrelated" - type = "list(string)" - default = jsonencode(["a", "b"]) - } - %s ` - tfCliConfigPath := downloadProviders(t, fmt.Sprintf(mainTfTemplate, "")) + tfCliConfigPath := downloadProviders(t, coderProviderTemplate) t.Setenv("TF_CLI_CONFIG_FILE", tfCliConfigPath) - for _, tc := range []struct { - name string - // tags to apply to the external provisioner - provisionerTags map[string]string - // tags to apply to the create template version request - createTemplateVersionRequestTags map[string]string - // the coder_workspace_tags bit of main.tf. - // you can add more stuff here if you need - tfWorkspaceTags string - templateImportUserVariableValues []codersdk.VariableValue - // if we need to set parameters on workspace build - workspaceBuildParameters []codersdk.WorkspaceBuildParameter - skipCreateWorkspace bool - }{ + for _, tc := range []testWorkspaceTagsTerraformCase{ { name: "no tags", tfWorkspaceTags: ``, @@ -2804,78 +2796,108 @@ func TestWorkspaceTagsTerraform(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - client, owner := coderdenttest.New(t, &coderdenttest.Options{ - Options: &coderdtest.Options{ - // We intentionally do not run a built-in provisioner daemon here. - IncludeProvisionerDaemon: true, - }, - LicenseOptions: &coderdenttest.LicenseOptions{ - Features: license.Features{ - codersdk.FeatureExternalProvisionerDaemons: 1, - }, - }, + t.Run("dynamic", func(t *testing.T) { + testWorkspaceTagsTerraform(t, tc, true) }) - templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin()) - member, memberUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) + t.Run("static", func(t *testing.T) { + testWorkspaceTagsTerraform(t, tc, false) + }) + }) + } +} - // This can take a while, so set a relatively long timeout. - ctx := testutil.Context(t, 2*testutil.WaitSuperLong) +func testWorkspaceTagsTerraform(t *testing.T, tc testWorkspaceTagsTerraformCase, dynamic bool) { + mainTfTemplate := ` + terraform { + required_providers { + coder = { + source = "coder/coder" + } + } + } - emptyTar := testutil.CreateTar(t, map[string]string{"main.tf": ""}) - emptyFi, err := templateAdmin.Upload(ctx, "application/x-tar", bytes.NewReader(emptyTar)) - require.NoError(t, err) + provider "coder" {} + data "coder_workspace" "me" {} + data "coder_workspace_owner" "me" {} + data "coder_parameter" "unrelated" { + name = "unrelated" + type = "list(string)" + default = jsonencode(["a", "b"]) + } + %s + ` - emptyTv, err := templateAdmin.CreateTemplateVersion(ctx, owner.OrganizationID, codersdk.CreateTemplateVersionRequest{ - Name: testutil.GetRandomName(t), - FileID: emptyFi.ID, - StorageMethod: codersdk.ProvisionerStorageMethodFile, - Provisioner: codersdk.ProvisionerTypeEcho, - }) - require.NoError(t, err) + client, owner := coderdenttest.New(t, &coderdenttest.Options{ + Options: &coderdtest.Options{ + // We intentionally do not run a built-in provisioner daemon here. + IncludeProvisionerDaemon: false, + }, + LicenseOptions: &coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureExternalProvisionerDaemons: 1, + }, + }, + }) + templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin()) + member, memberUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) - coderdtest.AwaitTemplateVersionJobCompleted(t, templateAdmin, emptyTv.ID) - tpl := coderdtest.CreateTemplate(t, templateAdmin, owner.OrganizationID, emptyTv.ID, func(request *codersdk.CreateTemplateRequest) { - request.UseClassicParameterFlow = ptr.Ref(false) - }) + // This can take a while, so set a relatively long timeout. + ctx := testutil.Context(t, 2*testutil.WaitSuperLong) - // The provisioner for the next template version - _ = coderdenttest.NewExternalProvisionerDaemonTerraform(t, client, owner.OrganizationID, tc.provisionerTags) - - // Creating a template as a template admin must succeed - templateFiles := map[string]string{"main.tf": fmt.Sprintf(mainTfTemplate, tc.tfWorkspaceTags)} - tarBytes := testutil.CreateTar(t, templateFiles) - fi, err := templateAdmin.Upload(ctx, "application/x-tar", bytes.NewReader(tarBytes)) - require.NoError(t, err, "failed to upload file") - tv, err := templateAdmin.CreateTemplateVersion(ctx, owner.OrganizationID, codersdk.CreateTemplateVersionRequest{ - Name: testutil.GetRandomName(t), - FileID: fi.ID, - StorageMethod: codersdk.ProvisionerStorageMethodFile, - Provisioner: codersdk.ProvisionerTypeTerraform, - ProvisionerTags: tc.createTemplateVersionRequestTags, - UserVariableValues: tc.templateImportUserVariableValues, - TemplateID: tpl.ID, - }) - require.NoError(t, err, "failed to create template version") - coderdtest.AwaitTemplateVersionJobCompleted(t, templateAdmin, tv.ID) + emptyTar := testutil.CreateTar(t, map[string]string{"main.tf": ""}) + emptyFi, err := templateAdmin.Upload(ctx, "application/x-tar", bytes.NewReader(emptyTar)) + require.NoError(t, err) - err = templateAdmin.UpdateActiveTemplateVersion(ctx, tpl.ID, codersdk.UpdateActiveTemplateVersion{ - ID: tv.ID, - }) - require.NoError(t, err, "set to active template version") - - if !tc.skipCreateWorkspace { - // Creating a workspace as a non-privileged user must succeed - ws, err := member.CreateUserWorkspace(ctx, memberUser.Username, codersdk.CreateWorkspaceRequest{ - TemplateID: tpl.ID, - Name: coderdtest.RandomUsername(t), - RichParameterValues: tc.workspaceBuildParameters, - }) - require.NoError(t, err, "failed to create workspace") - tagJSON, _ := json.Marshal(ws.LatestBuild.Job.Tags) - t.Logf("Created workspace build with tags: %s", tagJSON) - coderdtest.AwaitWorkspaceBuildJobCompleted(t, member, ws.LatestBuild.ID) - } - }) + // This template version does not need to succeed in being created. + // It will be in pending forever. We just need it to create a template. + emptyTv, err := templateAdmin.CreateTemplateVersion(ctx, owner.OrganizationID, codersdk.CreateTemplateVersionRequest{ + Name: testutil.GetRandomName(t), + FileID: emptyFi.ID, + StorageMethod: codersdk.ProvisionerStorageMethodFile, + Provisioner: codersdk.ProvisionerTypeTerraform, + }) + require.NoError(t, err) + + tpl := coderdtest.CreateTemplate(t, templateAdmin, owner.OrganizationID, emptyTv.ID, func(request *codersdk.CreateTemplateRequest) { + request.UseClassicParameterFlow = ptr.Ref(!dynamic) + }) + + // The provisioner for the next template version + _ = coderdenttest.NewExternalProvisionerDaemonTerraform(t, client, owner.OrganizationID, tc.provisionerTags) + + // Creating a template as a template admin must succeed + templateFiles := map[string]string{"main.tf": fmt.Sprintf(mainTfTemplate, tc.tfWorkspaceTags)} + tarBytes := testutil.CreateTar(t, templateFiles) + fi, err := templateAdmin.Upload(ctx, "application/x-tar", bytes.NewReader(tarBytes)) + require.NoError(t, err, "failed to upload file") + tv, err := templateAdmin.CreateTemplateVersion(ctx, owner.OrganizationID, codersdk.CreateTemplateVersionRequest{ + Name: testutil.GetRandomName(t), + FileID: fi.ID, + StorageMethod: codersdk.ProvisionerStorageMethodFile, + Provisioner: codersdk.ProvisionerTypeTerraform, + ProvisionerTags: tc.createTemplateVersionRequestTags, + UserVariableValues: tc.templateImportUserVariableValues, + TemplateID: tpl.ID, + }) + require.NoError(t, err, "failed to create template version") + coderdtest.AwaitTemplateVersionJobCompleted(t, templateAdmin, tv.ID) + + err = templateAdmin.UpdateActiveTemplateVersion(ctx, tpl.ID, codersdk.UpdateActiveTemplateVersion{ + ID: tv.ID, + }) + require.NoError(t, err, "set to active template version") + + if !tc.skipCreateWorkspace { + // Creating a workspace as a non-privileged user must succeed + ws, err := member.CreateUserWorkspace(ctx, memberUser.Username, codersdk.CreateWorkspaceRequest{ + TemplateID: tpl.ID, + Name: coderdtest.RandomUsername(t), + RichParameterValues: tc.workspaceBuildParameters, + }) + require.NoError(t, err, "failed to create workspace") + tagJSON, _ := json.Marshal(ws.LatestBuild.Job.Tags) + t.Logf("Created workspace build [%s] with tags: %s", ws.LatestBuild.Job.Type, tagJSON) + coderdtest.AwaitWorkspaceBuildJobCompleted(t, member, ws.LatestBuild.ID) } } From ac9fccb5700ce913809d6b6fec0b6f55841f0966 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 17 Jul 2025 11:54:04 -0600 Subject: [PATCH 11/14] assume default variable type of string --- coderd/coderdtest/coderdtest.go | 1 - coderd/dynamicparameters/variablevalues.go | 7 ++++++- coderd/parameters_test.go | 2 +- enterprise/coderd/workspaces_test.go | 7 +++++-- provisioner/terraform/parse.go | 4 ++++ 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index 1429a85da8b27..4aa968468e146 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -1075,7 +1075,6 @@ func AwaitWorkspaceBuildJobCompleted(t testing.TB, client *codersdk.Client, buil t.Logf("waiting for workspace build job %s", build) var workspaceBuild codersdk.WorkspaceBuild require.Eventually(t, func() bool { - var err error workspaceBuild, err = client.WorkspaceBuild(ctx, build) return assert.NoError(t, err) && workspaceBuild.Job.CompletedAt != nil diff --git a/coderd/dynamicparameters/variablevalues.go b/coderd/dynamicparameters/variablevalues.go index 1bb72cc2b0510..d63c7be2523e8 100644 --- a/coderd/dynamicparameters/variablevalues.go +++ b/coderd/dynamicparameters/variablevalues.go @@ -25,7 +25,12 @@ func VariableValues(vals []database.TemplateVersionVariable) (map[string]cty.Val var err error switch v.Type { - case "string": + // 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) diff --git a/coderd/parameters_test.go b/coderd/parameters_test.go index 19251ff2090d0..c00d6f9224bfb 100644 --- a/coderd/parameters_test.go +++ b/coderd/parameters_test.go @@ -360,7 +360,7 @@ func TestDynamicParametersWithTerraformValues(t *testing.T) { static: nil, }) - ctx := testutil.Context(t, testutil.WaitShort*100000) + ctx := testutil.Context(t, testutil.WaitShort) stream := setup.stream previews := stream.Chan() diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index 5565960dfeb4e..effebca2cb5e5 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -2799,7 +2799,10 @@ func TestWorkspaceTagsTerraform(t *testing.T) { t.Run("dynamic", func(t *testing.T) { testWorkspaceTagsTerraform(t, tc, true) }) - t.Run("static", func(t *testing.T) { + + // classic uses tfparse for tags. This sub test can be + // removed when tf parse is removed. + t.Run("classic", func(t *testing.T) { testWorkspaceTagsTerraform(t, tc, false) }) }) @@ -3172,7 +3175,7 @@ func TestWorkspaceLock(t *testing.T) { require.NotNil(t, workspace.DeletingAt) require.NotNil(t, workspace.DormantAt) require.Equal(t, workspace.DormantAt.Add(dormantTTL), *workspace.DeletingAt) - require.WithinRange(t, *workspace.DormantAt, time.Now().Add(-time.Second*10), time.Now()) + require.WithinRange(t, *workspace.DormantAt, time.Now().Add(-time.Second), time.Now()) // Locking a workspace shouldn't update the last_used_at. require.Equal(t, lastUsedAt, workspace.LastUsedAt) diff --git a/provisioner/terraform/parse.go b/provisioner/terraform/parse.go index 7aa78e401c503..d5b59df327f65 100644 --- a/provisioner/terraform/parse.go +++ b/provisioner/terraform/parse.go @@ -15,6 +15,10 @@ import ( ) // Parse extracts Terraform variables from source-code. +// TODO: This Parse is incomplete. It uses tfparse instead of terraform. +// The inputs are incomplete, as values such as the user context, parameters, +// etc are all important to the parsing process. This should be replaced with +// preview and have all inputs. func (s *server) Parse(sess *provisionersdk.Session, _ *proto.ParseRequest, _ <-chan struct{}) *proto.ParseComplete { ctx := sess.Context() _, span := s.startTrace(ctx, tracing.FuncName()) From 432021d15c71e3ac8b227c453e59df917ea3ad56 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 17 Jul 2025 11:58:39 -0600 Subject: [PATCH 12/14] rename unit test --- enterprise/coderd/workspaces_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index effebca2cb5e5..e5fe7372912c8 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -2803,13 +2803,13 @@ func TestWorkspaceTagsTerraform(t *testing.T) { // classic uses tfparse for tags. This sub test can be // removed when tf parse is removed. t.Run("classic", func(t *testing.T) { - testWorkspaceTagsTerraform(t, tc, false) + workspaceTagsTerraform(t, tc, false) }) }) } } -func testWorkspaceTagsTerraform(t *testing.T, tc testWorkspaceTagsTerraformCase, dynamic bool) { +func workspaceTagsTerraform(t *testing.T, tc testWorkspaceTagsTerraformCase, dynamic bool) { mainTfTemplate := ` terraform { required_providers { From 2347119da01acd0bb5fec29ec799eec4e4fb3b72 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 17 Jul 2025 12:02:19 -0600 Subject: [PATCH 13/14] fixup! rename unit test --- enterprise/coderd/workspaces_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index e5fe7372912c8..d622748899aa0 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -2797,7 +2797,7 @@ func TestWorkspaceTagsTerraform(t *testing.T) { } { t.Run(tc.name, func(t *testing.T) { t.Run("dynamic", func(t *testing.T) { - testWorkspaceTagsTerraform(t, tc, true) + workspaceTagsTerraform(t, tc, true) }) // classic uses tfparse for tags. This sub test can be From 7b2213cde7d7c2c679fef0b050895d3aad7ccb03 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 21 Jul 2025 09:59:49 -0500 Subject: [PATCH 14/14] add comments --- coderd/dynamicparameters/variablevalues.go | 2 ++ coderd/templateversions.go | 1 + 2 files changed, 3 insertions(+) diff --git a/coderd/dynamicparameters/variablevalues.go b/coderd/dynamicparameters/variablevalues.go index d63c7be2523e8..574039119c786 100644 --- a/coderd/dynamicparameters/variablevalues.go +++ b/coderd/dynamicparameters/variablevalues.go @@ -10,6 +10,8 @@ import ( "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 { diff --git a/coderd/templateversions.go b/coderd/templateversions.go index 9642b362d40f5..72b18a2c47e92 100644 --- a/coderd/templateversions.go +++ b/coderd/templateversions.go @@ -1802,6 +1802,7 @@ func (api *API) dynamicTemplateVersionTags(ctx context.Context, rw http.Response } // Pass in any manually specified template variables as TFVars. + // TODO: Does this break if the type is not a string? tfVarValues := make(map[string]cty.Value) for _, variable := range templateVariables { tfVarValues[variable.Name] = cty.StringVal(variable.Value) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy