mirror of
https://github.com/coder/coder.git
synced 2025-07-08 11:39:50 +00:00
fix(coderd/wsbuilder): correctly evaluate dynamic workspace tag values (#15897)
Relates to https://github.com/coder/coder/issues/15894: - Adds `coderdenttest.NewExternalProvisionerDaemonTerraform` - Adds integration-style test coverage for creating a workspace with `coder_workspace_tags` specified in `main.tf` - Modifies `coderd/wsbuilder` to fetch template version variables and includes them in eval context for evaluating `coder_workspace_tags`
This commit is contained in:
@ -1,8 +1,10 @@
|
||||
package coderd_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
@ -1420,6 +1422,182 @@ func TestTemplateDoesNotAllowUserAutostop(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// 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
|
||||
// successfully. The workspace itself does not specify any resources, and
|
||||
// this is fine.
|
||||
func TestWorkspaceTagsTerraform(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mainTfTemplate := `
|
||||
terraform {
|
||||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
}
|
||||
}
|
||||
}
|
||||
provider "coder" {}
|
||||
data "coder_workspace" "me" {}
|
||||
data "coder_workspace_owner" "me" {}
|
||||
%s
|
||||
`
|
||||
|
||||
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
|
||||
}{
|
||||
{
|
||||
name: "no tags",
|
||||
tfWorkspaceTags: ``,
|
||||
},
|
||||
{
|
||||
name: "empty tags",
|
||||
tfWorkspaceTags: `
|
||||
data "coder_workspace_tags" "tags" {
|
||||
tags = {}
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "static tag",
|
||||
provisionerTags: map[string]string{"foo": "bar"},
|
||||
tfWorkspaceTags: `
|
||||
data "coder_workspace_tags" "tags" {
|
||||
tags = {
|
||||
"foo" = "bar"
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "tag variable",
|
||||
provisionerTags: map[string]string{"foo": "bar"},
|
||||
tfWorkspaceTags: `
|
||||
variable "foo" {
|
||||
default = "bar"
|
||||
}
|
||||
data "coder_workspace_tags" "tags" {
|
||||
tags = {
|
||||
"foo" = var.foo
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "tag param",
|
||||
provisionerTags: map[string]string{"foo": "bar"},
|
||||
tfWorkspaceTags: `
|
||||
data "coder_parameter" "foo" {
|
||||
name = "foo"
|
||||
type = "string"
|
||||
default = "bar"
|
||||
}
|
||||
data "coder_workspace_tags" "tags" {
|
||||
tags = {
|
||||
"foo" = data.coder_parameter.foo.value
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "tag param with default from var",
|
||||
provisionerTags: map[string]string{"foo": "bar"},
|
||||
tfWorkspaceTags: `
|
||||
variable "foo" {
|
||||
type = string
|
||||
default = "bar"
|
||||
}
|
||||
data "coder_parameter" "foo" {
|
||||
name = "foo"
|
||||
type = "string"
|
||||
default = var.foo
|
||||
}
|
||||
data "coder_workspace_tags" "tags" {
|
||||
tags = {
|
||||
"foo" = data.coder_parameter.foo.value
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "override no tags",
|
||||
provisionerTags: map[string]string{"foo": "baz"},
|
||||
createTemplateVersionRequestTags: map[string]string{"foo": "baz"},
|
||||
tfWorkspaceTags: ``,
|
||||
},
|
||||
{
|
||||
name: "override empty tags",
|
||||
provisionerTags: map[string]string{"foo": "baz"},
|
||||
createTemplateVersionRequestTags: map[string]string{"foo": "baz"},
|
||||
tfWorkspaceTags: `
|
||||
data "coder_workspace_tags" "tags" {
|
||||
tags = {}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "does not override static tag",
|
||||
provisionerTags: map[string]string{"foo": "bar"},
|
||||
createTemplateVersionRequestTags: map[string]string{"foo": "baz"},
|
||||
tfWorkspaceTags: `
|
||||
data "coder_workspace_tags" "tags" {
|
||||
tags = {
|
||||
"foo" = "bar"
|
||||
}
|
||||
}`,
|
||||
},
|
||||
} {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitSuperLong)
|
||||
|
||||
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)
|
||||
|
||||
_ = 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,
|
||||
})
|
||||
require.NoError(t, err, "failed to create template version")
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, templateAdmin, tv.ID)
|
||||
tpl := coderdtest.CreateTemplate(t, templateAdmin, owner.OrganizationID, tv.ID)
|
||||
|
||||
// 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),
|
||||
})
|
||||
require.NoError(t, err, "failed to create workspace")
|
||||
coderdtest.AwaitWorkspaceBuildJobCompleted(t, member, ws.LatestBuild.ID)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Blocked by autostart requirements
|
||||
func TestExecutorAutostartBlocked(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
Reference in New Issue
Block a user