mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: include template variables in dynamic parameter rendering (#18819)
Closes https://github.com/coder/coder/issues/18671 Template variables now loaded into dynamic parameters.
This commit is contained in:
@@ -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,33 +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: ``,
|
||||
@@ -2803,56 +2796,114 @@ 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: false,
|
||||
},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureExternalProvisionerDaemons: 1,
|
||||
},
|
||||
},
|
||||
t.Run("dynamic", func(t *testing.T) {
|
||||
workspaceTagsTerraform(t, tc, true)
|
||||
})
|
||||
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)
|
||||
|
||||
// This can take a while, so set a relatively long timeout.
|
||||
ctx := testutil.Context(t, 2*testutil.WaitSuperLong)
|
||||
|
||||
// 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,
|
||||
// classic uses tfparse for tags. This sub test can be
|
||||
// removed when tf parse is removed.
|
||||
t.Run("classic", func(t *testing.T) {
|
||||
workspaceTagsTerraform(t, tc, false)
|
||||
})
|
||||
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
|
||||
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")
|
||||
coderdtest.AwaitWorkspaceBuildJobCompleted(t, member, ws.LatestBuild.ID)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func workspaceTagsTerraform(t *testing.T, tc testWorkspaceTagsTerraformCase, dynamic bool) {
|
||||
mainTfTemplate := `
|
||||
terraform {
|
||||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
`
|
||||
|
||||
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)
|
||||
|
||||
// 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))
|
||||
require.NoError(t, err)
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
// downloadProviders is a test helper that creates a temporary file and writes a
|
||||
// terraform CLI config file with a provider_installation stanza for coder/coder
|
||||
// using dev_overrides. It also fetches the latest provider release from GitHub
|
||||
@@ -3124,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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user