diff --git a/cli/restart.go b/cli/restart.go index 156f506105c5a..20ee0b9b9de9d 100644 --- a/cli/restart.go +++ b/cli/restart.go @@ -51,8 +51,17 @@ func (r *RootCmd) restart() *serpent.Command { return err } + stopParamValues, err := asWorkspaceBuildParameters(parameterFlags.ephemeralParameters) + if err != nil { + return xerrors.Errorf("parse ephemeral parameters: %w", err) + } wbr := codersdk.CreateWorkspaceBuildRequest{ Transition: codersdk.WorkspaceTransitionStop, + // Ephemeral parameters should be passed to both stop and start builds. + // TODO: maybe these values should be sourced from the previous build? + // It has to be manually sourced, as ephemeral parameters do not carry across + // builds. + RichParameterValues: stopParamValues, } if bflags.provisionerLogDebug { wbr.LogLevel = codersdk.ProvisionerLogLevelDebug diff --git a/cli/restart_test.go b/cli/restart_test.go index d69344435bf28..01be7e590cebf 100644 --- a/cli/restart_test.go +++ b/cli/restart_test.go @@ -10,6 +10,7 @@ import ( "github.com/coder/coder/v2/cli/clitest" "github.com/coder/coder/v2/coderd/coderdtest" + "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/provisioner/echo" "github.com/coder/coder/v2/provisionersdk/proto" @@ -70,8 +71,14 @@ func TestRestart(t *testing.T) { member, memberUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, echoResponses()) coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) - template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID) - workspace := coderdtest.CreateWorkspace(t, member, template.ID) + template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(request *codersdk.CreateTemplateRequest) { + request.UseClassicParameterFlow = ptr.Ref(true) // TODO: Remove when dynamic parameters prompt missing ephemeral parameters. + }) + workspace := coderdtest.CreateWorkspace(t, member, template.ID, func(request *codersdk.CreateWorkspaceRequest) { + request.RichParameterValues = []codersdk.WorkspaceBuildParameter{ + {Name: ephemeralParameterName, Value: "placeholder"}, + } + }) coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID) inv, root := clitest.New(t, "restart", workspace.Name, "--prompt-ephemeral-parameters") @@ -125,7 +132,11 @@ func TestRestart(t *testing.T) { version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, echoResponses()) coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID) - workspace := coderdtest.CreateWorkspace(t, member, template.ID) + workspace := coderdtest.CreateWorkspace(t, member, template.ID, func(request *codersdk.CreateWorkspaceRequest) { + request.RichParameterValues = []codersdk.WorkspaceBuildParameter{ + {Name: ephemeralParameterName, Value: "placeholder"}, + } + }) coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID) inv, root := clitest.New(t, "restart", workspace.Name, @@ -178,8 +189,14 @@ func TestRestart(t *testing.T) { member, memberUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, echoResponses()) coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) - template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID) - workspace := coderdtest.CreateWorkspace(t, member, template.ID) + template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(request *codersdk.CreateTemplateRequest) { + request.UseClassicParameterFlow = ptr.Ref(true) // TODO: Remove when dynamic parameters prompts missing ephemeral parameters + }) + workspace := coderdtest.CreateWorkspace(t, member, template.ID, func(request *codersdk.CreateWorkspaceRequest) { + request.RichParameterValues = []codersdk.WorkspaceBuildParameter{ + {Name: ephemeralParameterName, Value: "placeholder"}, + } + }) coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID) inv, root := clitest.New(t, "restart", workspace.Name, "--build-options") @@ -233,7 +250,11 @@ func TestRestart(t *testing.T) { version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, echoResponses()) coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID) - workspace := coderdtest.CreateWorkspace(t, member, template.ID) + workspace := coderdtest.CreateWorkspace(t, member, template.ID, func(request *codersdk.CreateWorkspaceRequest) { + request.RichParameterValues = []codersdk.WorkspaceBuildParameter{ + {Name: ephemeralParameterName, Value: "placeholder"}, + } + }) coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID) inv, root := clitest.New(t, "restart", workspace.Name, diff --git a/cli/start_test.go b/cli/start_test.go index 85b7b88374f72..6e58b40e30778 100644 --- a/cli/start_test.go +++ b/cli/start_test.go @@ -113,10 +113,18 @@ func TestStart(t *testing.T) { version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, echoResponses()) coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID) - workspace := coderdtest.CreateWorkspace(t, member, template.ID) + workspace := coderdtest.CreateWorkspace(t, member, template.ID, func(request *codersdk.CreateWorkspaceRequest) { + request.RichParameterValues = []codersdk.WorkspaceBuildParameter{ + {Name: ephemeralParameterName, Value: "foo"}, // Value is required, set it to something + } + }) coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID) // Stop the workspace - workspaceBuild := coderdtest.CreateWorkspaceBuild(t, client, workspace, database.WorkspaceTransitionStop) + workspaceBuild := coderdtest.CreateWorkspaceBuild(t, client, workspace, database.WorkspaceTransitionStop, func(request *codersdk.CreateWorkspaceBuildRequest) { + request.RichParameterValues = []codersdk.WorkspaceBuildParameter{ + {Name: ephemeralParameterName, Value: "foo"}, // Value is required, set it to something + } + }) coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspaceBuild.ID) inv, root := clitest.New(t, "start", workspace.Name, "--prompt-ephemeral-parameters") @@ -167,10 +175,18 @@ func TestStart(t *testing.T) { version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, echoResponses()) coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID) - workspace := coderdtest.CreateWorkspace(t, member, template.ID) + workspace := coderdtest.CreateWorkspace(t, member, template.ID, func(request *codersdk.CreateWorkspaceRequest) { + request.RichParameterValues = []codersdk.WorkspaceBuildParameter{ + {Name: ephemeralParameterName, Value: "foo"}, // Value is required, set it to something + } + }) coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID) // Stop the workspace - workspaceBuild := coderdtest.CreateWorkspaceBuild(t, client, workspace, database.WorkspaceTransitionStop) + workspaceBuild := coderdtest.CreateWorkspaceBuild(t, client, workspace, database.WorkspaceTransitionStop, func(request *codersdk.CreateWorkspaceBuildRequest) { + request.RichParameterValues = []codersdk.WorkspaceBuildParameter{ + {Name: ephemeralParameterName, Value: "foo"}, // Value is required, set it to something + } + }) coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspaceBuild.ID) inv, root := clitest.New(t, "start", workspace.Name, diff --git a/cli/templatepush_test.go b/cli/templatepush_test.go index f7a31d5e0c25f..732fdd5ee50b0 100644 --- a/cli/templatepush_test.go +++ b/cli/templatepush_test.go @@ -509,6 +509,7 @@ func TestTemplatePush(t *testing.T) { default = "1" } data "coder_parameter" "b" { + name = "b" type = string default = "2" } diff --git a/cli/testdata/coder_list_--output_json.golden b/cli/testdata/coder_list_--output_json.golden index 51c2887cd1e4a..822998329be5b 100644 --- a/cli/testdata/coder_list_--output_json.golden +++ b/cli/testdata/coder_list_--output_json.golden @@ -15,7 +15,7 @@ "template_allow_user_cancel_workspace_jobs": false, "template_active_version_id": "============[version ID]============", "template_require_active_version": false, - "template_use_classic_parameter_flow": true, + "template_use_classic_parameter_flow": false, "latest_build": { "id": "========[workspace build ID]========", "created_at": "====[timestamp]=====", diff --git a/cli/update_test.go b/cli/update_test.go index 7a7480353c01d..b80218f49ab45 100644 --- a/cli/update_test.go +++ b/cli/update_test.go @@ -182,7 +182,7 @@ func TestUpdateWithRichParameters(t *testing.T) { {Name: firstParameterName, Description: firstParameterDescription, Mutable: true}, {Name: immutableParameterName, Description: immutableParameterDescription, Mutable: false}, {Name: secondParameterName, Description: secondParameterDescription, Mutable: true}, - {Name: ephemeralParameterName, Description: ephemeralParameterDescription, Mutable: true, Ephemeral: true}, + {Name: ephemeralParameterName, Description: ephemeralParameterDescription, Mutable: true, Ephemeral: true, DefaultValue: "unset"}, }) } @@ -811,7 +811,9 @@ func TestUpdateValidateRichParameters(t *testing.T) { } version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, prepareEchoResponses(templateParameters)) coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) - template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID) + template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(request *codersdk.CreateTemplateRequest) { + request.UseClassicParameterFlow = ptr.Ref(true) // TODO: Remove when dynamic parameters can pass this test + }) // Create new workspace inv, root := clitest.New(t, "create", "my-workspace", "--yes", "--template", template.Name, "--parameter", fmt.Sprintf("%s=%s", numberParameterName, tempVal)) diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go index 812d2e2576650..0ccefff2c42a9 100644 --- a/coderd/database/dbgen/dbgen.go +++ b/coderd/database/dbgen/dbgen.go @@ -147,7 +147,7 @@ func Template(t testing.TB, db database.Store, seed database.Template) database. DisplayName: takeFirst(seed.DisplayName, testutil.GetRandomName(t)), AllowUserCancelWorkspaceJobs: seed.AllowUserCancelWorkspaceJobs, MaxPortSharingLevel: takeFirst(seed.MaxPortSharingLevel, database.AppSharingLevelOwner), - UseClassicParameterFlow: takeFirst(seed.UseClassicParameterFlow, true), + UseClassicParameterFlow: takeFirst(seed.UseClassicParameterFlow, false), }) require.NoError(t, err, "insert template") diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index bbd6ca3ce5736..67d58ad05c802 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -1750,7 +1750,7 @@ CREATE TABLE templates ( deprecated text DEFAULT ''::text NOT NULL, activity_bump bigint DEFAULT '3600000000000'::bigint NOT NULL, max_port_sharing_level app_sharing_level DEFAULT 'owner'::app_sharing_level NOT NULL, - use_classic_parameter_flow boolean DEFAULT true NOT NULL + use_classic_parameter_flow boolean DEFAULT false NOT NULL ); COMMENT ON COLUMN templates.default_ttl IS 'The default duration for autostop for workspaces created from this template.'; diff --git a/coderd/database/migrations/000352_default_dynamic_templates.down.sql b/coderd/database/migrations/000352_default_dynamic_templates.down.sql new file mode 100644 index 0000000000000..548cd7e2c30b2 --- /dev/null +++ b/coderd/database/migrations/000352_default_dynamic_templates.down.sql @@ -0,0 +1 @@ +ALTER TABLE templates ALTER COLUMN use_classic_parameter_flow SET DEFAULT true; diff --git a/coderd/database/migrations/000352_default_dynamic_templates.up.sql b/coderd/database/migrations/000352_default_dynamic_templates.up.sql new file mode 100644 index 0000000000000..51bcab9f099f8 --- /dev/null +++ b/coderd/database/migrations/000352_default_dynamic_templates.up.sql @@ -0,0 +1 @@ +ALTER TABLE templates ALTER COLUMN use_classic_parameter_flow SET DEFAULT false; diff --git a/coderd/insights_test.go b/coderd/insights_test.go index ded030351a3b3..d916b20fea26e 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -665,10 +665,11 @@ func TestTemplateInsights_Golden(t *testing.T) { // where we can control the template ID. // createdTemplate := coderdtest.CreateTemplate(t, client, firstUser.OrganizationID, version.ID) createdTemplate := dbgen.Template(t, db, database.Template{ - ID: template.id, - ActiveVersionID: version.ID, - OrganizationID: firstUser.OrganizationID, - CreatedBy: firstUser.UserID, + ID: template.id, + ActiveVersionID: version.ID, + OrganizationID: firstUser.OrganizationID, + CreatedBy: firstUser.UserID, + UseClassicParameterFlow: true, // Required for testing classic parameter flow behavior GroupACL: database.TemplateACL{ firstUser.OrganizationID.String(): db2sdk.TemplateRoleActions(codersdk.TemplateRoleUse), }, @@ -1556,10 +1557,11 @@ func TestUserActivityInsights_Golden(t *testing.T) { // where we can control the template ID. // createdTemplate := coderdtest.CreateTemplate(t, client, firstUser.OrganizationID, version.ID) createdTemplate := dbgen.Template(t, db, database.Template{ - ID: template.id, - ActiveVersionID: version.ID, - OrganizationID: firstUser.OrganizationID, - CreatedBy: firstUser.UserID, + ID: template.id, + ActiveVersionID: version.ID, + OrganizationID: firstUser.OrganizationID, + CreatedBy: firstUser.UserID, + UseClassicParameterFlow: true, // Required for parameter usage tracking in this test GroupACL: database.TemplateACL{ firstUser.OrganizationID.String(): db2sdk.TemplateRoleActions(codersdk.TemplateRoleUse), }, diff --git a/coderd/parameters_test.go b/coderd/parameters_test.go index c00d6f9224bfb..07c00d2ef23e3 100644 --- a/coderd/parameters_test.go +++ b/coderd/parameters_test.go @@ -3,10 +3,12 @@ package coderd_test import ( "context" "os" + "sync" "testing" "github.com/google/uuid" "github.com/stretchr/testify/require" + "go.uber.org/atomic" "golang.org/x/xerrors" "github.com/coder/coder/v2/coderd" @@ -199,8 +201,15 @@ func TestDynamicParametersWithTerraformValues(t *testing.T) { modulesArchive, err := terraform.GetModulesArchive(os.DirFS("testdata/parameters/modules")) require.NoError(t, err) + c := atomic.NewInt32(0) + reject := &dbRejectGitSSHKey{Store: db, hook: func(d *dbRejectGitSSHKey) { + if c.Add(1) > 1 { + // Second call forward, reject + d.SetReject(true) + } + }} setup := setupDynamicParamsTest(t, setupDynamicParamsTestParams{ - db: &dbRejectGitSSHKey{Store: db}, + db: reject, ps: ps, provisionerDaemonVersion: provProto.CurrentVersion.String(), mainTF: dynamicParametersTerraformSource, @@ -444,8 +453,30 @@ func setupDynamicParamsTest(t *testing.T, args setupDynamicParamsTestParams) dyn // that is generally impossible to force an error. type dbRejectGitSSHKey struct { database.Store + rejectMu sync.RWMutex + reject bool + hook func(d *dbRejectGitSSHKey) +} + +// SetReject toggles whether GetGitSSHKey should return an error or passthrough to the underlying store. +func (d *dbRejectGitSSHKey) SetReject(reject bool) { + d.rejectMu.Lock() + defer d.rejectMu.Unlock() + d.reject = reject } -func (*dbRejectGitSSHKey) GetGitSSHKey(_ context.Context, _ uuid.UUID) (database.GitSSHKey, error) { - return database.GitSSHKey{}, xerrors.New("forcing a fake error") +func (d *dbRejectGitSSHKey) GetGitSSHKey(ctx context.Context, userID uuid.UUID) (database.GitSSHKey, error) { + if d.hook != nil { + d.hook(d) + } + + d.rejectMu.RLock() + reject := d.reject + d.rejectMu.RUnlock() + + if reject { + return database.GitSSHKey{}, xerrors.New("forcing a fake error") + } + + return d.Store.GetGitSSHKey(ctx, userID) } diff --git a/coderd/templates.go b/coderd/templates.go index bba38bb033614..60f94e5cd29cc 100644 --- a/coderd/templates.go +++ b/coderd/templates.go @@ -197,8 +197,8 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque return } - // Default is true until dynamic parameters are promoted to stable. - useClassicParameterFlow := ptr.NilToDefault(createTemplate.UseClassicParameterFlow, true) + // Default is false as dynamic parameters are now the preferred approach. + useClassicParameterFlow := ptr.NilToDefault(createTemplate.UseClassicParameterFlow, false) // Make a temporary struct to represent the template. This is used for // auditing if any of the following checks fail. It will be overwritten when diff --git a/coderd/templates_test.go b/coderd/templates_test.go index 5e7fcea75609d..0858ce83325cc 100644 --- a/coderd/templates_test.go +++ b/coderd/templates_test.go @@ -77,7 +77,7 @@ func TestPostTemplateByOrganization(t *testing.T) { assert.Equal(t, expected.Name, got.Name) assert.Equal(t, expected.Description, got.Description) assert.Equal(t, expected.ActivityBumpMillis, got.ActivityBumpMillis) - assert.Equal(t, expected.UseClassicParameterFlow, true) // Current default is true + assert.Equal(t, expected.UseClassicParameterFlow, false) // Current default is false require.Len(t, auditor.AuditLogs(), 3) assert.Equal(t, database.AuditActionCreate, auditor.AuditLogs()[0].Action) @@ -1551,7 +1551,7 @@ func TestPatchTemplateMeta(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) - require.True(t, template.UseClassicParameterFlow, "default is true") + require.False(t, template.UseClassicParameterFlow, "default is false") bTrue := true bFalse := false diff --git a/coderd/templateversions.go b/coderd/templateversions.go index e787a6b813b18..cc106b390f73c 100644 --- a/coderd/templateversions.go +++ b/coderd/templateversions.go @@ -1471,7 +1471,7 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht return } - var dynamicTemplate bool + dynamicTemplate := true // Default to using dynamic templates if req.TemplateID != uuid.Nil { tpl, err := api.Database.GetTemplateByID(ctx, req.TemplateID) if httpapi.Is404Error(err) { diff --git a/coderd/templateversions_test.go b/coderd/templateversions_test.go index 1ad06bae38aee..912bca1c5fec1 100644 --- a/coderd/templateversions_test.go +++ b/coderd/templateversions_test.go @@ -275,6 +275,7 @@ func TestPostTemplateVersionsByOrganization(t *testing.T) { files map[string]string reqTags map[string]string wantTags map[string]string + variables []codersdk.VariableValue expectError string }{ { @@ -290,6 +291,7 @@ func TestPostTemplateVersionsByOrganization(t *testing.T) { default = "1" } data "coder_parameter" "b" { + name = "b" type = string default = "2" } @@ -311,6 +313,7 @@ func TestPostTemplateVersionsByOrganization(t *testing.T) { default = "1" } data "coder_parameter" "b" { + name = "b" type = string default = "2" } @@ -335,6 +338,7 @@ func TestPostTemplateVersionsByOrganization(t *testing.T) { default = "1" } data "coder_parameter" "b" { + name = "b" type = string default = "2" } @@ -365,6 +369,7 @@ func TestPostTemplateVersionsByOrganization(t *testing.T) { default = "1" } data "coder_parameter" "b" { + name = "b" type = string default = "2" } @@ -395,6 +400,7 @@ func TestPostTemplateVersionsByOrganization(t *testing.T) { default = "1" } data "coder_parameter" "b" { + name = "b" type = string default = "2" } @@ -429,11 +435,12 @@ func TestPostTemplateVersionsByOrganization(t *testing.T) { } }`, }, - reqTags: map[string]string{"a": "b"}, - wantTags: map[string]string{"owner": "", "scope": "organization", "a": "b"}, + reqTags: map[string]string{"a": "b"}, + wantTags: map[string]string{"owner": "", "scope": "organization", "a": "b"}, + variables: []codersdk.VariableValue{{Name: "a", Value: "b"}}, }, { - name: "main.tf with disallowed workspace tag value", + name: "main.tf with resource reference", files: map[string]string{ `main.tf`: ` variable "a" { @@ -441,6 +448,7 @@ func TestPostTemplateVersionsByOrganization(t *testing.T) { default = "1" } data "coder_parameter" "b" { + name = "b" type = string default = "2" } @@ -461,38 +469,8 @@ func TestPostTemplateVersionsByOrganization(t *testing.T) { } }`, }, - expectError: `Unknown variable; There is no variable named "null_resource".`, - }, - { - name: "main.tf with disallowed function in tag value", - files: map[string]string{ - `main.tf`: ` - variable "a" { - type = string - default = "1" - } - data "coder_parameter" "b" { - type = string - default = "2" - } - data "coder_parameter" "unrelated" { - name = "unrelated" - type = "list(string)" - default = jsonencode(["a", "b"]) - } - resource "null_resource" "test" { - name = "foo" - } - data "coder_workspace_tags" "tags" { - tags = { - "foo": "bar", - "a": var.a, - "b": data.coder_parameter.b.value, - "test": pathexpand("~/file.txt"), - } - }`, - }, - expectError: `function "pathexpand" may not be used here`, + reqTags: map[string]string{"foo": "bar", "a": "1", "b": "2", "test": "foo"}, + wantTags: map[string]string{"owner": "", "scope": "organization", "foo": "bar", "a": "1", "b": "2", "test": "foo"}, }, // We will allow coder_workspace_tags to set the scope on a template version import job // BUT the user ID will be ultimately determined by the API key in the scope. @@ -618,11 +596,12 @@ func TestPostTemplateVersionsByOrganization(t *testing.T) { // Create a template version from the archive tvName := testutil.GetRandomNameHyphenated(t) tv, err := templateAdmin.CreateTemplateVersion(ctx, owner.OrganizationID, codersdk.CreateTemplateVersionRequest{ - Name: tvName, - StorageMethod: codersdk.ProvisionerStorageMethodFile, - Provisioner: codersdk.ProvisionerTypeTerraform, - FileID: fi.ID, - ProvisionerTags: tt.reqTags, + Name: tvName, + StorageMethod: codersdk.ProvisionerStorageMethodFile, + Provisioner: codersdk.ProvisionerTypeTerraform, + FileID: fi.ID, + ProvisionerTags: tt.reqTags, + UserVariableValues: tt.variables, }) if tt.expectError == "" { diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index 141c62ff3a4b3..9fe066aae6284 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -431,9 +431,9 @@ func TestWorkspace(t *testing.T) { // Test Utility variables templateVersionParameters := []*proto.RichParameter{ - {Name: "param1", Type: "string", Required: false}, - {Name: "param2", Type: "string", Required: false}, - {Name: "param3", Type: "string", Required: false}, + {Name: "param1", Type: "string", Required: false, DefaultValue: "default1"}, + {Name: "param2", Type: "string", Required: false, DefaultValue: "default2"}, + {Name: "param3", Type: "string", Required: false, DefaultValue: "default3"}, } presetParameters := []*proto.PresetParameter{ {Name: "param1", Value: "value1"}, @@ -3842,7 +3842,9 @@ func TestWorkspaceWithEphemeralRichParameters(t *testing.T) { }}, }) coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) - template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(request *codersdk.CreateTemplateRequest) { + request.UseClassicParameterFlow = ptr.Ref(true) // TODO: Remove this when dynamic parameters handles this case + }) // Create workspace with default values workspace := coderdtest.CreateWorkspace(t, client, template.ID) diff --git a/provisioner/echo/serve.go b/provisioner/echo/serve.go index 031af97317aca..4bb2a1dd6b78b 100644 --- a/provisioner/echo/serve.go +++ b/provisioner/echo/serve.go @@ -8,6 +8,7 @@ import ( "os" "path/filepath" "strings" + "text/template" "github.com/google/uuid" "golang.org/x/xerrors" @@ -377,6 +378,45 @@ func TarWithOptions(ctx context.Context, logger slog.Logger, responses *Response logger.Debug(context.Background(), "extra file written", slog.F("name", name), slog.F("bytes_written", n)) } + + // Write a main.tf with the appropriate parameters. This is to write terraform + // that matches the parameters defined in the responses. Dynamic parameters + // parsed these, even in the echo provisioner. + var mainTF bytes.Buffer + for _, respPlan := range responses.ProvisionPlan { + plan := respPlan.GetPlan() + if plan == nil { + continue + } + + for _, param := range plan.Parameters { + paramTF, err := ParameterTerraform(param) + if err != nil { + return nil, xerrors.Errorf("parameter terraform: %w", err) + } + _, _ = mainTF.WriteString(paramTF) + } + } + + if mainTF.Len() > 0 { + mainTFData := ` +terraform { + required_providers { + coder = { + source = "coder/coder" + } + } +} +` + mainTF.String() + + _ = writer.WriteHeader(&tar.Header{ + Name: `main.tf`, + Size: int64(len(mainTFData)), + Mode: 0o644, + }) + _, _ = writer.Write([]byte(mainTFData)) + } + // `writer.Close()` function flushes the writer buffer, and adds extra padding to create a legal tarball. err := writer.Close() if err != nil { @@ -385,6 +425,69 @@ func TarWithOptions(ctx context.Context, logger slog.Logger, responses *Response return buffer.Bytes(), nil } +// ParameterTerraform will create a Terraform data block for the provided parameter. +func ParameterTerraform(param *proto.RichParameter) (string, error) { + tmpl := template.Must(template.New("parameter").Funcs(map[string]any{ + "showValidation": func(v *proto.RichParameter) bool { + return v != nil && (v.ValidationMax != nil || v.ValidationMin != nil || + v.ValidationError != "" || v.ValidationRegex != "" || + v.ValidationMonotonic != "") + }, + "formType": func(v *proto.RichParameter) string { + s, _ := proto.ProviderFormType(v.FormType) + return string(s) + }, + }).Parse(` +data "coder_parameter" "{{ .Name }}" { + name = "{{ .Name }}" + display_name = "{{ .DisplayName }}" + description = "{{ .Description }}" + icon = "{{ .Icon }}" + mutable = {{ .Mutable }} + ephemeral = {{ .Ephemeral }} + order = {{ .Order }} +{{- if .DefaultValue }} + default = {{ .DefaultValue }} +{{- end }} +{{- if .Type }} + type = "{{ .Type }}" +{{- end }} +{{- if .FormType }} + form_type = "{{ formType . }}" +{{- end }} +{{- range .Options }} + option { + name = "{{ .Name }}" + value = "{{ .Value }}" + } +{{- end }} +{{- if showValidation .}} + validation { + {{- if .ValidationRegex }} + regex = "{{ .ValidationRegex }}" + {{- end }} + {{- if .ValidationError }} + error = "{{ .ValidationError }}" + {{- end }} + {{- if .ValidationMin }} + min = {{ .ValidationMin }} + {{- end }} + {{- if .ValidationMax }} + max = {{ .ValidationMax }} + {{- end }} + {{- if .ValidationMonotonic }} + monotonic = "{{ .ValidationMonotonic }}" + {{- end }} + } +{{- end }} +} +`)) + + var buf bytes.Buffer + err := tmpl.Execute(&buf, param) + return buf.String(), err +} + func WithResources(resources []*proto.Resource) *Responses { return &Responses{ Parse: ParseComplete, diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts index a738899b25f2c..768a7d477f992 100644 --- a/site/e2e/helpers.ts +++ b/site/e2e/helpers.ts @@ -1203,3 +1203,36 @@ export async function addUserToOrganization( } await page.mouse.click(10, 10); // close the popover by clicking outside of it } + +/** + * disableDynamicParameters navigates to the template settings page and disables + * dynamic parameters by unchecking the "Enable dynamic parameters" checkbox. + */ +export const disableDynamicParameters = async ( + page: Page, + templateName: string, + orgName = defaultOrganizationName, +) => { + await page.goto(`/templates/${orgName}/${templateName}/settings`, { + waitUntil: "domcontentloaded", + }); + + // Find and uncheck the "Enable dynamic parameters" checkbox + const dynamicParamsCheckbox = page.getByRole("checkbox", { + name: /Enable dynamic parameters for workspace creation/, + }); + + // If the checkbox is checked, uncheck it + if (await dynamicParamsCheckbox.isChecked()) { + await dynamicParamsCheckbox.click(); + } + + // Save the changes + await page.getByRole("button", { name: /save/i }).click(); + + // Wait for the success message or page to update + await page.waitForSelector("text=Template updated successfully", { + state: "visible", + timeout: 10000, + }); +}; diff --git a/site/e2e/tests/workspaces/createWorkspace.spec.ts b/site/e2e/tests/workspaces/createWorkspace.spec.ts index 452c6e9969f37..e9d2d5efcca6f 100644 --- a/site/e2e/tests/workspaces/createWorkspace.spec.ts +++ b/site/e2e/tests/workspaces/createWorkspace.spec.ts @@ -4,6 +4,7 @@ import { StarterTemplates, createTemplate, createWorkspace, + disableDynamicParameters, echoResponsesWithParameters, login, openTerminalWindow, @@ -35,6 +36,9 @@ test("create workspace", async ({ page }) => { apply: [{ apply: { resources: [{ name: "example" }] } }], }); + // Disable dynamic parameters to use classic parameter flow for this test + await disableDynamicParameters(page, template); + await login(page, users.member); await createWorkspace(page, template); }); @@ -51,6 +55,9 @@ test("create workspace with default immutable parameters", async ({ page }) => { echoResponsesWithParameters(richParameters), ); + // Disable dynamic parameters to use classic parameter flow for this test + await disableDynamicParameters(page, template); + await login(page, users.member); const workspaceName = await createWorkspace(page, template); await verifyParameters(page, workspaceName, richParameters, [ @@ -68,6 +75,9 @@ test("create workspace with default mutable parameters", async ({ page }) => { echoResponsesWithParameters(richParameters), ); + // Disable dynamic parameters to use classic parameter flow for this test + await disableDynamicParameters(page, template); + await login(page, users.member); const workspaceName = await createWorkspace(page, template); await verifyParameters(page, workspaceName, richParameters, [ @@ -95,6 +105,9 @@ test("create workspace with default and required parameters", async ({ echoResponsesWithParameters(richParameters), ); + // Disable dynamic parameters to use classic parameter flow for this test + await disableDynamicParameters(page, template); + await login(page, users.member); const workspaceName = await createWorkspace(page, template, { richParameters, @@ -127,6 +140,9 @@ test("create workspace and overwrite default parameters", async ({ page }) => { echoResponsesWithParameters(richParameters), ); + // Disable dynamic parameters to use classic parameter flow for this test + await disableDynamicParameters(page, template); + await login(page, users.member); const workspaceName = await createWorkspace(page, template, { richParameters, @@ -147,6 +163,9 @@ test("create workspace with disable_param search params", async ({ page }) => { echoResponsesWithParameters(richParameters), ); + // Disable dynamic parameters to use classic parameter flow for this test + await disableDynamicParameters(page, templateName); + await login(page, users.member); await page.goto( `/templates/${templateName}/workspace?disable_params=first_parameter,second_parameter`, @@ -165,6 +184,9 @@ test.skip("create docker workspace", async ({ context, page }) => { await login(page, users.templateAdmin); const template = await createTemplate(page, StarterTemplates.STARTER_DOCKER); + // Disable dynamic parameters to use classic parameter flow for this test + await disableDynamicParameters(page, template); + await login(page, users.member); const workspaceName = await createWorkspace(page, template); diff --git a/site/e2e/tests/workspaces/restartWorkspace.spec.ts b/site/e2e/tests/workspaces/restartWorkspace.spec.ts index 444ff891f0fdc..2ec24c6d251bf 100644 --- a/site/e2e/tests/workspaces/restartWorkspace.spec.ts +++ b/site/e2e/tests/workspaces/restartWorkspace.spec.ts @@ -4,6 +4,7 @@ import { buildWorkspaceWithParameters, createTemplate, createWorkspace, + disableDynamicParameters, echoResponsesWithParameters, verifyParameters, } from "../../helpers"; @@ -24,6 +25,9 @@ test("restart workspace with ephemeral parameters", async ({ page }) => { echoResponsesWithParameters(richParameters), ); + // Disable dynamic parameters to use classic parameter flow for this test + await disableDynamicParameters(page, template); + await login(page, users.member); const workspaceName = await createWorkspace(page, template); diff --git a/site/e2e/tests/workspaces/startWorkspace.spec.ts b/site/e2e/tests/workspaces/startWorkspace.spec.ts index 90fac440046ea..ea8a5c21c88bd 100644 --- a/site/e2e/tests/workspaces/startWorkspace.spec.ts +++ b/site/e2e/tests/workspaces/startWorkspace.spec.ts @@ -4,6 +4,7 @@ import { buildWorkspaceWithParameters, createTemplate, createWorkspace, + disableDynamicParameters, echoResponsesWithParameters, stopWorkspace, verifyParameters, @@ -25,6 +26,9 @@ test("start workspace with ephemeral parameters", async ({ page }) => { echoResponsesWithParameters(richParameters), ); + // Disable dynamic parameters to use classic parameter flow for this test + await disableDynamicParameters(page, template); + await login(page, users.member); const workspaceName = await createWorkspace(page, template); diff --git a/site/e2e/tests/workspaces/updateWorkspace.spec.ts b/site/e2e/tests/workspaces/updateWorkspace.spec.ts index 48c341eb63956..8a242a2dc7238 100644 --- a/site/e2e/tests/workspaces/updateWorkspace.spec.ts +++ b/site/e2e/tests/workspaces/updateWorkspace.spec.ts @@ -3,6 +3,7 @@ import { users } from "../../constants"; import { createTemplate, createWorkspace, + disableDynamicParameters, echoResponsesWithParameters, updateTemplate, updateWorkspace, @@ -34,6 +35,9 @@ test("update workspace, new optional, immutable parameter added", async ({ echoResponsesWithParameters(richParameters), ); + // Disable dynamic parameters to use classic parameter flow for this test + await disableDynamicParameters(page, template); + await login(page, users.member); const workspaceName = await createWorkspace(page, template); @@ -77,6 +81,9 @@ test("update workspace, new required, mutable parameter added", async ({ echoResponsesWithParameters(richParameters), ); + // Disable dynamic parameters to use classic parameter flow for this test + await disableDynamicParameters(page, template); + await login(page, users.member); const workspaceName = await createWorkspace(page, template); @@ -122,6 +129,9 @@ test("update workspace with ephemeral parameter enabled", async ({ page }) => { echoResponsesWithParameters(richParameters), ); + // Disable dynamic parameters to use classic parameter flow for this test + await disableDynamicParameters(page, template); + await login(page, users.member); const workspaceName = await createWorkspace(page, template); diff --git a/site/src/modules/workspaces/ClassicParameterFlowDeprecationWarning/ClassicParameterFlowDeprecationWarning.test.tsx b/site/src/modules/workspaces/ClassicParameterFlowDeprecationWarning/ClassicParameterFlowDeprecationWarning.test.tsx new file mode 100644 index 0000000000000..f68bb273f26a0 --- /dev/null +++ b/site/src/modules/workspaces/ClassicParameterFlowDeprecationWarning/ClassicParameterFlowDeprecationWarning.test.tsx @@ -0,0 +1,39 @@ +import { render, screen } from "@testing-library/react"; +import { ClassicParameterFlowDeprecationWarning } from "./ClassicParameterFlowDeprecationWarning"; + +jest.mock("modules/navigation", () => ({ + useLinks: () => () => "/mock-link", + linkToTemplate: () => "/mock-template-link", +})); + +describe("ClassicParameterFlowDeprecationWarning", () => { + const defaultProps = { + organizationName: "test-org", + templateName: "test-template", + }; + + it("renders warning when enabled and user has template update permissions", () => { + render( + , + ); + + expect(screen.getByText("deprecated")).toBeInTheDocument(); + expect(screen.getByText("Go to Template Settings")).toBeInTheDocument(); + }); + + it("does not render when enabled is false", () => { + const { container } = render( + , + ); + + expect(container.firstChild).toBeNull(); + }); +}); diff --git a/site/src/modules/workspaces/ClassicParameterFlowDeprecationWarning/ClassicParameterFlowDeprecationWarning.tsx b/site/src/modules/workspaces/ClassicParameterFlowDeprecationWarning/ClassicParameterFlowDeprecationWarning.tsx new file mode 100644 index 0000000000000..d6afd3be464bf --- /dev/null +++ b/site/src/modules/workspaces/ClassicParameterFlowDeprecationWarning/ClassicParameterFlowDeprecationWarning.tsx @@ -0,0 +1,38 @@ +import { Alert } from "components/Alert/Alert"; +import { Link } from "components/Link/Link"; +import type { FC } from "react"; +import { docs } from "utils/docs"; + +interface ClassicParameterFlowDeprecationWarningProps { + templateSettingsLink: string; + isEnabled: boolean; +} + +export const ClassicParameterFlowDeprecationWarning: FC< + ClassicParameterFlowDeprecationWarningProps +> = ({ templateSettingsLink, isEnabled }) => { + if (!isEnabled) { + return null; + } + + return ( + +
+ This template is using the classic parameter flow, which will be{" "} + deprecated and removed in a future release. Please + migrate to{" "} + + dynamic parameters + {" "} + on template settings for improved functionality. +
+ + + Go to Template Settings + +
+ ); +}; diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx index 6d057a73d1a50..b4c4c281c1b0d 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx @@ -72,6 +72,20 @@ const CreateWorkspacePage: FC = () => { }), enabled: !!templateQuery.data, }); + const templatePermissionsQuery = useQuery({ + ...checkAuthorization({ + checks: { + canUpdateTemplate: { + object: { + resource_type: "template", + resource_id: templateQuery.data?.id ?? "", + }, + action: "update", + }, + }, + }), + enabled: !!templateQuery.data, + }); const realizedVersionId = customVersionId ?? templateQuery.data?.active_version_id; const organizationId = templateQuery.data?.organization_id; @@ -93,9 +107,13 @@ const CreateWorkspacePage: FC = () => { const isLoadingFormData = templateQuery.isLoading || permissionsQuery.isLoading || + templatePermissionsQuery.isLoading || richParametersQuery.isLoading; const loadFormDataError = - templateQuery.error ?? permissionsQuery.error ?? richParametersQuery.error; + templateQuery.error ?? + permissionsQuery.error ?? + templatePermissionsQuery.error ?? + richParametersQuery.error; const title = autoCreateWorkspaceMutation.isPending ? "Creating workspace..." @@ -211,7 +229,9 @@ const CreateWorkspacePage: FC = () => { startPollingExternalAuth={startPollingExternalAuth} hasAllRequiredExternalAuth={hasAllRequiredExternalAuth} permissions={permissionsQuery.data as CreateWorkspacePermissions} - canUpdateTemplate={permissionsQuery.data?.canUpdateTemplate} + templatePermissions={ + templatePermissionsQuery.data as { canUpdateTemplate: boolean } + } parameters={realizedParameters as TemplateVersionParameter[]} presets={templateVersionPresetsQuery.data ?? []} creatingWorkspace={createWorkspaceMutation.isPending} diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx index 907c5e6861f68..4d0e3ff81c95f 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx @@ -11,6 +11,7 @@ import { MockUserOwner, mockApiError, } from "testHelpers/entities"; +import { withDashboardProvider } from "testHelpers/storybook"; import { CreateWorkspacePageView } from "./CreateWorkspacePageView"; const meta: Meta = { @@ -31,7 +32,9 @@ const meta: Meta = { canUpdateTemplate: false, }, onCancel: action("onCancel"), + templatePermissions: { canUpdateTemplate: true }, }, + decorators: [withDashboardProvider], }; export default meta; diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx index ceac49988c0a5..bc31e1db42742 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx @@ -28,6 +28,8 @@ import { UserAutocomplete } from "components/UserAutocomplete/UserAutocomplete"; import { type FormikContextType, useFormik } from "formik"; import type { ExternalAuthPollingState } from "hooks/useExternalAuth"; import { ExternalLinkIcon } from "lucide-react"; +import { linkToTemplate, useLinks } from "modules/navigation"; +import { ClassicParameterFlowDeprecationWarning } from "modules/workspaces/ClassicParameterFlowDeprecationWarning/ClassicParameterFlowDeprecationWarning"; import { generateWorkspaceName } from "modules/workspaces/generateWorkspaceName"; import { type FC, useCallback, useEffect, useMemo, useState } from "react"; import { Link } from "react-router-dom"; @@ -68,6 +70,7 @@ interface CreateWorkspacePageViewProps { autofillParameters: AutofillBuildParameter[]; presets: TypesGen.Preset[]; permissions: CreateWorkspacePermissions; + templatePermissions: { canUpdateTemplate: boolean }; creatingWorkspace: boolean; canUpdateTemplate?: boolean; onCancel: () => void; @@ -94,11 +97,13 @@ export const CreateWorkspacePageView: FC = ({ autofillParameters, presets = [], permissions, + templatePermissions, creatingWorkspace, canUpdateTemplate, onSubmit, onCancel, }) => { + const getLink = useLinks(); const [owner, setOwner] = useState(defaultOwner); const [suggestedName, setSuggestedName] = useState(() => generateWorkspaceName(), @@ -261,6 +266,13 @@ export const CreateWorkspacePageView: FC = ({ + + View docs @@ -555,7 +555,7 @@ export const CreateWorkspacePageViewExperimental: FC< parameters cannot be modified once the workspace is created. View docs diff --git a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx index 677984e5e9e5a..359058f78761a 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx @@ -245,19 +245,20 @@ export const TemplateSettingsForm: FC = ({ label={ - Enable dynamic parameters for workspace creation + Enable dynamic parameters for workspace creation (recommended)
- The new workspace form allows you to design your template - with new form types and identity-aware conditional - parameters. The form will only present options that are - compatible and available. + The dynamic workspace form allows you to design your + template with additional form types and identity-aware + conditional parameters. This is the default option for new + templates. The classic workspace creation flow will be + deprecated in a future release.
Learn more diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersForm.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersForm.tsx index 00b8c2ae8464b..68dc6e65b7595 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersForm.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersForm.tsx @@ -14,6 +14,7 @@ import { import { RichParameterInput } from "components/RichParameterInput/RichParameterInput"; import { Spinner } from "components/Spinner/Spinner"; import { useFormik } from "formik"; +import { ClassicParameterFlowDeprecationWarning } from "modules/workspaces/ClassicParameterFlowDeprecationWarning/ClassicParameterFlowDeprecationWarning"; import type { FC } from "react"; import { getFormHelpers } from "utils/formUtils"; import { @@ -33,6 +34,7 @@ interface WorkspaceParameterFormProps { autofillParams: AutofillBuildParameter[]; isSubmitting: boolean; canChangeVersions: boolean; + templatePermissions: { canUpdateTemplate: boolean } | undefined; error: unknown; onCancel: () => void; onSubmit: (values: WorkspaceParametersFormValues) => void; @@ -46,6 +48,7 @@ export const WorkspaceParametersForm: FC = ({ autofillParams, error, canChangeVersions, + templatePermissions, isSubmitting, }) => { const form = useFormik({ @@ -81,12 +84,15 @@ export const WorkspaceParametersForm: FC = ({ return ( <> {disabled && ( - + The template for this workspace requires automatic updates. Update the workspace to edit parameters. )} - + {hasNonEphemeralParameters && ( { const permissions = permissionsQuery.data as WorkspacePermissions | undefined; const canChangeVersions = Boolean(permissions?.updateWorkspaceVersion); + const templatePermissionsQuery = useQuery({ + ...checkAuthorization({ + checks: { + canUpdateTemplate: { + object: { + resource_type: "template", + resource_id: workspace.template_id, + }, + action: "update", + }, + }, + }), + enabled: workspace !== undefined, + }); + + const templatePermissions = templatePermissionsQuery.data as + | { canUpdateTemplate: boolean } + | undefined; + return ( <> @@ -60,6 +79,7 @@ const WorkspaceParametersPage: FC = () => { { type WorkspaceParametersPageViewProps = { workspace: Workspace; canChangeVersions: boolean; + templatePermissions: { canUpdateTemplate: boolean } | undefined; data: Awaited> | undefined; submitError: unknown; isSubmitting: boolean; @@ -106,6 +127,7 @@ export const WorkspaceParametersPageView: FC< > = ({ workspace, canChangeVersions, + templatePermissions, data, submitError, onSubmit, @@ -129,6 +151,7 @@ export const WorkspaceParametersPageView: FC< ({ ...p, source: "active_build", diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageExperimental.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageExperimental.tsx index 803dc4ff4fd48..1415b1b2bc5f1 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageExperimental.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageExperimental.tsx @@ -226,7 +226,7 @@ const WorkspaceParametersPageExperimental: FC = () => {
View docs @@ -261,7 +261,9 @@ const WorkspaceParametersPageExperimental: FC = () => { message="This workspace has no parameters" cta={ Learn more about parameters diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageViewExperimental.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageViewExperimental.tsx index 14253ad51f827..52228f19d9f40 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageViewExperimental.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageViewExperimental.tsx @@ -210,7 +210,7 @@ export const WorkspaceParametersPageViewExperimental: FC< parameters cannot be modified once the workspace is created. View docs diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index a80e3b623a211..44e729e7f4d4f 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -826,7 +826,7 @@ export const MockTemplate: TypesGen.Template = { deprecated: false, deprecation_message: "", max_port_share_level: "public", - use_classic_parameter_flow: true, + use_classic_parameter_flow: false, }; const MockTemplateVersionFiles: TemplateVersionFiles = {