mirror of
https://github.com/coder/coder.git
synced 2025-07-09 11:45:56 +00:00
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:
@ -429,9 +429,8 @@ 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) {
|
||||||
|
@ -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`
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
@ -1488,8 +1488,11 @@ func TestWorkspaceTagsTerraform(t *testing.T) {
|
|||||||
createTemplateVersionRequestTags map[string]string
|
createTemplateVersionRequestTags map[string]string
|
||||||
// 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
|
||||||
skipCreateWorkspace bool
|
templateImportUserVariableValues []codersdk.VariableValue
|
||||||
|
// if we need to set parameters on workspace build
|
||||||
|
workspaceBuildParameters []codersdk.WorkspaceBuildParameter
|
||||||
|
skipCreateWorkspace bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "no tags",
|
name: "no tags",
|
||||||
@ -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) {
|
||||||
@ -1617,11 +1652,12 @@ func TestWorkspaceTagsTerraform(t *testing.T) {
|
|||||||
fi, err := templateAdmin.Upload(ctx, "application/x-tar", bytes.NewReader(tarBytes))
|
fi, err := templateAdmin.Upload(ctx, "application/x-tar", bytes.NewReader(tarBytes))
|
||||||
require.NoError(t, err, "failed to upload file")
|
require.NoError(t, err, "failed to upload file")
|
||||||
tv, err := templateAdmin.CreateTemplateVersion(ctx, owner.OrganizationID, codersdk.CreateTemplateVersionRequest{
|
tv, err := templateAdmin.CreateTemplateVersion(ctx, owner.OrganizationID, codersdk.CreateTemplateVersionRequest{
|
||||||
Name: testutil.GetRandomName(t),
|
Name: testutil.GetRandomName(t),
|
||||||
FileID: fi.ID,
|
FileID: fi.ID,
|
||||||
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)
|
||||||
@ -1630,8 +1666,9 @@ func TestWorkspaceTagsTerraform(t *testing.T) {
|
|||||||
if !tc.skipCreateWorkspace {
|
if !tc.skipCreateWorkspace {
|
||||||
// Creating a workspace as a non-privileged user must succeed
|
// Creating a workspace as a non-privileged user must succeed
|
||||||
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)
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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",
|
||||||
|
Reference in New Issue
Block a user