fix(provisioner/terraform/tfparse): allow empty values in coder_workspace_tag defaults (#16303)

* chore(docs): update docs re workspace tag default values
* chore(coderdenttest): use random name instead of t.Name() in newExternalProvisionerDaemon
* fix(provisioner/terraform/tfparse): allow empty values in coder_workspace_tag defaults
This commit is contained in:
Cian Johnston
2025-01-28 09:11:39 +00:00
committed by GitHub
parent f5186699ad
commit 76adde91dc
8 changed files with 90 additions and 32 deletions

View File

@ -430,8 +430,7 @@ func TestPostTemplateVersionsByOrganization(t *testing.T) {
}`, }`,
}, },
reqTags: map[string]string{"a": "b"}, reqTags: map[string]string{"a": "b"},
// wantTags: map[string]string{"owner": "", "scope": "organization", "a": "b"}, wantTags: map[string]string{"owner": "", "scope": "organization", "a": "b"},
expectError: `provisioner tag "a" evaluated to an empty value`,
}, },
{ {
name: "main.tf with disallowed workspace tag value", name: "main.tf with disallowed workspace tag value",
@ -568,6 +567,42 @@ func TestPostTemplateVersionsByOrganization(t *testing.T) {
}, },
wantTags: map[string]string{"owner": "", "scope": "organization"}, 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 tt := tt
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View File

@ -62,11 +62,6 @@ variables and parameters. This is illustrated in the table below:
## Constraints ## 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 ### Tagged provisioners
It is possible to choose tag combinations that no provisioner can handle. This 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 #### Not supported
- Function calls: `try(var.foo, "default")` - Function calls that reference files on disk: `abspath`, `file*`, `pathexpand`
- Resources: `compute_instance.dev.name` - Resources: `compute_instance.dev.name`
- Data sources other than `coder_parameter`: `data.local_file.hostname.content` - Data sources other than `coder_parameter`: `data.local_file.hostname.content`

View File

@ -389,7 +389,7 @@ func newExternalProvisionerDaemon(t testing.TB, client *codersdk.Client, org uui
daemon := provisionerd.New(func(ctx context.Context) (provisionerdproto.DRPCProvisionerDaemonClient, error) { daemon := provisionerd.New(func(ctx context.Context) (provisionerdproto.DRPCProvisionerDaemonClient, error) {
return client.ServeProvisionerDaemon(ctx, codersdk.ServeProvisionerDaemonRequest{ return client.ServeProvisionerDaemon(ctx, codersdk.ServeProvisionerDaemonRequest{
ID: uuid.New(), ID: uuid.New(),
Name: t.Name(), Name: testutil.GetRandomName(t),
Organization: org, Organization: org,
Provisioners: []codersdk.ProvisionerType{provisionerType}, Provisioners: []codersdk.ProvisionerType{provisionerType},
Tags: tags, Tags: tags,

View File

@ -285,7 +285,7 @@ func TestProvisionerDaemonServe(t *testing.T) {
daemons, err := client.ProvisionerDaemons(context.Background()) daemons, err := client.ProvisionerDaemons(context.Background())
assert.NoError(t, err, "failed to get provisioner daemons") assert.NoError(t, err, "failed to get provisioner daemons")
return len(daemons) > 0 && 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, provisionersdk.ScopeUser, daemons[0].Tags[provisionersdk.TagScope]) &&
assert.Equal(t, user.UserID.String(), daemons[0].Tags[provisionersdk.TagOwner]) assert.Equal(t, user.UserID.String(), daemons[0].Tags[provisionersdk.TagOwner])
}, testutil.WaitShort, testutil.IntervalMedium) }, testutil.WaitShort, testutil.IntervalMedium)

View File

@ -1489,6 +1489,9 @@ func TestWorkspaceTagsTerraform(t *testing.T) {
// the coder_workspace_tags bit of main.tf. // the coder_workspace_tags bit of main.tf.
// you can add more stuff here if you need // you can add more stuff here if you need
tfWorkspaceTags string tfWorkspaceTags string
templateImportUserVariableValues []codersdk.VariableValue
// if we need to set parameters on workspace build
workspaceBuildParameters []codersdk.WorkspaceBuildParameter
skipCreateWorkspace bool skipCreateWorkspace bool
}{ }{
{ {
@ -1589,6 +1592,38 @@ func TestWorkspaceTagsTerraform(t *testing.T) {
// matching tag foo=bar. // matching tag foo=bar.
skipCreateWorkspace: true, 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 tc := tc
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
@ -1622,6 +1657,7 @@ func TestWorkspaceTagsTerraform(t *testing.T) {
StorageMethod: codersdk.ProvisionerStorageMethodFile, StorageMethod: codersdk.ProvisionerStorageMethodFile,
Provisioner: codersdk.ProvisionerTypeTerraform, Provisioner: codersdk.ProvisionerTypeTerraform,
ProvisionerTags: tc.createTemplateVersionRequestTags, ProvisionerTags: tc.createTemplateVersionRequestTags,
UserVariableValues: tc.templateImportUserVariableValues,
}) })
require.NoError(t, err, "failed to create template version") require.NoError(t, err, "failed to create template version")
coderdtest.AwaitTemplateVersionJobCompleted(t, templateAdmin, tv.ID) coderdtest.AwaitTemplateVersionJobCompleted(t, templateAdmin, tv.ID)
@ -1632,6 +1668,7 @@ func TestWorkspaceTagsTerraform(t *testing.T) {
ws, err := member.CreateUserWorkspace(ctx, memberUser.Username, codersdk.CreateWorkspaceRequest{ ws, err := member.CreateUserWorkspace(ctx, memberUser.Username, codersdk.CreateWorkspaceRequest{
TemplateID: tpl.ID, TemplateID: tpl.ID,
Name: coderdtest.RandomUsername(t), Name: coderdtest.RandomUsername(t),
RichParameterValues: tc.workspaceBuildParameters,
}) })
require.NoError(t, err, "failed to create workspace") require.NoError(t, err, "failed to create workspace")
coderdtest.AwaitWorkspaceBuildJobCompleted(t, member, ws.LatestBuild.ID) coderdtest.AwaitWorkspaceBuildJobCompleted(t, member, ws.LatestBuild.ID)

View File

@ -7,7 +7,7 @@ icon: /icon/docker.png
## Overview ## 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 ## Use case
@ -18,10 +18,8 @@ By using `coder_workspace_tags` and `coder_parameter`s, template administrators
## Notes ## 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. - 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. - 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.
- You must specify default values for all data sources and variables referenced by the `coder_workspace_tags` data source.
See [Workspace Tags](https://coder.com/docs/templates/workspace-tags) for more information.
## Development ## Development

View File

@ -239,13 +239,6 @@ func (p *Parser) WorkspaceTagDefaults(ctx context.Context) (map[string]string, e
return nil, xerrors.Errorf("eval provisioner tags: %w", err) 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 return evalTags, nil
} }

View File

@ -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", name: "main.tf with missing parameter default value outside workspace tags",