From 413d1c845bdca428a7b50b1a5d39ddc3f7475be4 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 27 Jan 2025 18:39:10 +0000 Subject: [PATCH 1/3] fix(provisioner/terraform/tfparse): allow empty values in coder_workspace_tag defaults --- coderd/templateversions_test.go | 41 +++++++++++++- enterprise/coderd/workspaces_test.go | 55 ++++++++++++++++--- provisioner/terraform/tfparse/tfparse.go | 7 --- provisioner/terraform/tfparse/tfparse_test.go | 2 +- 4 files changed, 85 insertions(+), 20 deletions(-) diff --git a/coderd/templateversions_test.go b/coderd/templateversions_test.go index 3282defb968c5..9fd1bf6e2d830 100644 --- a/coderd/templateversions_test.go +++ b/coderd/templateversions_test.go @@ -429,9 +429,8 @@ func TestPostTemplateVersionsByOrganization(t *testing.T) { } }`, }, - reqTags: map[string]string{"a": "b"}, - // wantTags: map[string]string{"owner": "", "scope": "organization", "a": "b"}, - expectError: `provisioner tag "a" evaluated to an empty value`, + reqTags: map[string]string{"a": "b"}, + wantTags: map[string]string{"owner": "", "scope": "organization", "a": "b"}, }, { name: "main.tf with disallowed workspace tag value", @@ -568,6 +567,42 @@ func TestPostTemplateVersionsByOrganization(t *testing.T) { }, wantTags: map[string]string{"owner": "", "scope": "organization"}, }, + { + name: "main.tf with tags from parameter with default value from variable no default", + files: map[string]string{ + `main.tf`: ` + variable "provisioner" { + type = string + } + variable "default_provisioner" { + type = string + default = "" # intentionally blank, set on template creation + } + data "coder_parameter" "provisioner" { + name = "provisioner" + mutable = false + default = var.default_provisioner + dynamic "option" { + for_each = toset(split(",", var.provisioner)) + content { + name = option.value + value = option.value + } + } + } + data "coder_workspace_tags" "tags" { + tags = { + "provisioner" : data.coder_parameter.provisioner.value + } + }`, + }, + reqTags: map[string]string{ + "provisioner": "alpha", + }, + wantTags: map[string]string{ + "provisioner": "alpha", "owner": "", "scope": "organization", + }, + }, } { tt := tt t.Run(tt.name, func(t *testing.T) { diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index e2149603135c8..eedd6f1bcfa1c 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -1488,8 +1488,11 @@ func TestWorkspaceTagsTerraform(t *testing.T) { createTemplateVersionRequestTags map[string]string // the coder_workspace_tags bit of main.tf. // you can add more stuff here if you need - tfWorkspaceTags string - skipCreateWorkspace bool + tfWorkspaceTags string + templateImportUserVariableValues []codersdk.VariableValue + // if we need to set parameters on workspace build + workspaceBuildParameters []codersdk.WorkspaceBuildParameter + skipCreateWorkspace bool }{ { name: "no tags", @@ -1589,6 +1592,38 @@ func TestWorkspaceTagsTerraform(t *testing.T) { // matching tag foo=bar. skipCreateWorkspace: true, }, + { + name: "overrides with dynamic option from var", + provisionerTags: map[string]string{"foo": "bar"}, + createTemplateVersionRequestTags: map[string]string{"foo": "bar"}, + templateImportUserVariableValues: []codersdk.VariableValue{{Name: "default_foo", Value: "baz"}, {Name: "foo", Value: "bar,baz"}}, + workspaceBuildParameters: []codersdk.WorkspaceBuildParameter{{Name: "foo", Value: "bar"}}, + tfWorkspaceTags: ` + variable "default_foo" { + type = string + } + variable "foo" { + type = string + } + data "coder_parameter" "foo" { + name = "foo" + type = "string" + default = var.default_foo + mutable = false + dynamic "option" { + for_each = toset(split(",", var.foo)) + content { + name = option.value + value = option.value + } + } + } + data "coder_workspace_tags" "tags" { + tags = { + "foo" = data.coder_parameter.foo.value + } + }`, + }, } { tc := tc t.Run(tc.name, func(t *testing.T) { @@ -1617,11 +1652,12 @@ func TestWorkspaceTagsTerraform(t *testing.T) { 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, + Name: testutil.GetRandomName(t), + FileID: fi.ID, + StorageMethod: codersdk.ProvisionerStorageMethodFile, + Provisioner: codersdk.ProvisionerTypeTerraform, + ProvisionerTags: tc.createTemplateVersionRequestTags, + UserVariableValues: tc.templateImportUserVariableValues, }) require.NoError(t, err, "failed to create template version") coderdtest.AwaitTemplateVersionJobCompleted(t, templateAdmin, tv.ID) @@ -1630,8 +1666,9 @@ 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, - Name: coderdtest.RandomUsername(t), + TemplateID: tpl.ID, + Name: coderdtest.RandomUsername(t), + RichParameterValues: tc.workspaceBuildParameters, }) require.NoError(t, err, "failed to create workspace") coderdtest.AwaitWorkspaceBuildJobCompleted(t, member, ws.LatestBuild.ID) diff --git a/provisioner/terraform/tfparse/tfparse.go b/provisioner/terraform/tfparse/tfparse.go index 901f52babbdf1..281ce55f99146 100644 --- a/provisioner/terraform/tfparse/tfparse.go +++ b/provisioner/terraform/tfparse/tfparse.go @@ -239,13 +239,6 @@ func (p *Parser) WorkspaceTagDefaults(ctx context.Context) (map[string]string, e return nil, xerrors.Errorf("eval provisioner tags: %w", err) } - // Ensure that none of the tag values are empty after evaluation. - for k, v := range evalTags { - if len(strings.TrimSpace(v)) > 0 { - continue - } - return nil, xerrors.Errorf("provisioner tag %q evaluated to an empty value, please set a default value", k) - } return evalTags, nil } diff --git a/provisioner/terraform/tfparse/tfparse_test.go b/provisioner/terraform/tfparse/tfparse_test.go index 6f36d653c697c..ceefc484b2169 100644 --- a/provisioner/terraform/tfparse/tfparse_test.go +++ b/provisioner/terraform/tfparse/tfparse_test.go @@ -268,7 +268,7 @@ func Test_WorkspaceTagDefaultsFromFile(t *testing.T) { } }`, }, - expectError: `provisioner tag "az" evaluated to an empty value, please set a default value`, + expectTags: map[string]string{"cluster": "developers", "az": "", "platform": "kubernetes", "region": "us"}, }, { name: "main.tf with missing parameter default value outside workspace tags", From 85a168e23eb1ec2ebe150e0be0bb7783acc60d1e Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 27 Jan 2025 18:39:45 +0000 Subject: [PATCH 2/3] chore(coderdenttest): use random name instead of t.Name() in newExternalProvisionerDaemon --- enterprise/coderd/coderdenttest/coderdenttest.go | 2 +- enterprise/coderd/provisionerdaemons_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/enterprise/coderd/coderdenttest/coderdenttest.go b/enterprise/coderd/coderdenttest/coderdenttest.go index 0d44937e4a82d..d76722b5bac1a 100644 --- a/enterprise/coderd/coderdenttest/coderdenttest.go +++ b/enterprise/coderd/coderdenttest/coderdenttest.go @@ -389,7 +389,7 @@ func newExternalProvisionerDaemon(t testing.TB, client *codersdk.Client, org uui daemon := provisionerd.New(func(ctx context.Context) (provisionerdproto.DRPCProvisionerDaemonClient, error) { return client.ServeProvisionerDaemon(ctx, codersdk.ServeProvisionerDaemonRequest{ ID: uuid.New(), - Name: t.Name(), + Name: testutil.GetRandomName(t), Organization: org, Provisioners: []codersdk.ProvisionerType{provisionerType}, Tags: tags, diff --git a/enterprise/coderd/provisionerdaemons_test.go b/enterprise/coderd/provisionerdaemons_test.go index 9efb002a8e910..ff3ce625707ee 100644 --- a/enterprise/coderd/provisionerdaemons_test.go +++ b/enterprise/coderd/provisionerdaemons_test.go @@ -285,7 +285,7 @@ func TestProvisionerDaemonServe(t *testing.T) { daemons, err := client.ProvisionerDaemons(context.Background()) assert.NoError(t, err, "failed to get provisioner daemons") return len(daemons) > 0 && - assert.Equal(t, t.Name(), daemons[0].Name) && + assert.NotEmpty(t, daemons[0].Name) && assert.Equal(t, provisionersdk.ScopeUser, daemons[0].Tags[provisionersdk.TagScope]) && assert.Equal(t, user.UserID.String(), daemons[0].Tags[provisionersdk.TagOwner]) }, testutil.WaitShort, testutil.IntervalMedium) From ecb2ffd963b5f2f93b5d993e75fa6dae5be4f6b5 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 27 Jan 2025 20:32:02 +0000 Subject: [PATCH 3/3] chore(docs): update docs re workspace tag default values --- docs/admin/templates/extending-templates/workspace-tags.md | 7 +------ examples/workspace-tags/README.md | 6 ++---- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/docs/admin/templates/extending-templates/workspace-tags.md b/docs/admin/templates/extending-templates/workspace-tags.md index e49957d9ba515..96691fe162540 100644 --- a/docs/admin/templates/extending-templates/workspace-tags.md +++ b/docs/admin/templates/extending-templates/workspace-tags.md @@ -62,11 +62,6 @@ variables and parameters. This is illustrated in the table below: ## Constraints -### Default Values - -All template variables and `coder_parameter` data sources **must** provide a -default value. Failure to do so will result in an error. - ### Tagged provisioners It is possible to choose tag combinations that no provisioner can handle. This @@ -127,6 +122,6 @@ variables, and references to other resources. #### Not supported -- Function calls: `try(var.foo, "default")` +- Function calls that reference files on disk: `abspath`, `file*`, `pathexpand` - Resources: `compute_instance.dev.name` - Data sources other than `coder_parameter`: `data.local_file.hostname.content` diff --git a/examples/workspace-tags/README.md b/examples/workspace-tags/README.md index 386aab6262e47..4e9ac06643cee 100644 --- a/examples/workspace-tags/README.md +++ b/examples/workspace-tags/README.md @@ -7,7 +7,7 @@ icon: /icon/docker.png ## Overview -This Coder template presents use of [Workspace Tags](https://coder.com/docs/templates/workspace-tags) [Coder Parameters](https://coder.com/docs/templates/parameters). +This Coder template presents use of [Workspace Tags](https://coder.com/docs/admin/templates/extending-templates/workspace-tags) and [Coder Parameters](https://coder.com/docs/templates/parameters). ## Use case @@ -18,10 +18,8 @@ By using `coder_workspace_tags` and `coder_parameter`s, template administrators ## Notes - You will need to have an [external provisioner](https://coder.com/docs/admin/provisioners#external-provisioners) with the correct tagset running in order to import this template. -- When specifying values for the `coder_workspace_tags` data source, you are restricted to using a subset of Terraform's capabilities. -- You must specify default values for all data sources and variables referenced by the `coder_workspace_tags` data source. +- When specifying values for the `coder_workspace_tags` data source, you are restricted to using a subset of Terraform's capabilities. See [here](https://coder.com/docs/admin/templates/extending-templates/workspace-tags) for more details. -See [Workspace Tags](https://coder.com/docs/templates/workspace-tags) for more information. ## Development