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 = { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy