diff --git a/cli/parameterresolver.go b/cli/parameterresolver.go index f4ed240993538..8cae66527d503 100644 --- a/cli/parameterresolver.go +++ b/cli/parameterresolver.go @@ -184,7 +184,7 @@ func (pr *ParameterResolver) resolveWithInput(resolved []codersdk.WorkspaceBuild if (tvp.Ephemeral && pr.promptBuildOptions) || (action == WorkspaceCreate && tvp.Required) || (action == WorkspaceCreate && !tvp.Ephemeral) || - (action == WorkspaceUpdate && tvp.Required) || + (action == WorkspaceUpdate && tvp.Mutable && tvp.Required) || (action == WorkspaceUpdate && !tvp.Mutable && firstTimeUse) || (action == WorkspaceUpdate && tvp.Mutable && !tvp.Ephemeral && pr.promptRichParameters) { parameterValue, err := cliui.RichParameter(inv, tvp) diff --git a/cli/update_test.go b/cli/update_test.go index e799557fcc454..a066eaef7278c 100644 --- a/cli/update_test.go +++ b/cli/update_test.go @@ -602,4 +602,135 @@ func TestUpdateValidateRichParameters(t *testing.T) { } <-doneChan }) + + t.Run("ImmutableRequiredParameterExists_MutableRequiredParameterAdded", func(t *testing.T) { + t.Parallel() + + // Create template and workspace + client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) + user := coderdtest.CreateFirstUser(t, client) + + templateParameters := []*proto.RichParameter{ + {Name: stringParameterName, Type: "string", Mutable: false, Required: true, Options: []*proto.RichParameterOption{ + {Name: "First option", Description: "This is first option", Value: "1st"}, + {Name: "Second option", Description: "This is second option", Value: "2nd"}, + {Name: "Third option", Description: "This is third option", Value: "3rd"}, + }}, + } + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, prepareEchoResponses(templateParameters)) + coderdtest.AwaitTemplateVersionJob(t, client, version.ID) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + + inv, root := clitest.New(t, "create", "my-workspace", "--yes", "--template", template.Name, "--parameter", fmt.Sprintf("%s=%s", stringParameterName, "2nd")) + clitest.SetupConfig(t, client, root) + err := inv.Run() + require.NoError(t, err) + + // Update template: add required, mutable parameter + const mutableParameterName = "foobar" + updatedTemplateParameters := []*proto.RichParameter{ + templateParameters[0], + {Name: mutableParameterName, Type: "string", Mutable: true, Required: true}, + } + + updatedVersion := coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, prepareEchoResponses(updatedTemplateParameters), template.ID) + coderdtest.AwaitTemplateVersionJob(t, client, updatedVersion.ID) + err = client.UpdateActiveTemplateVersion(context.Background(), template.ID, codersdk.UpdateActiveTemplateVersion{ + ID: updatedVersion.ID, + }) + require.NoError(t, err) + + // Update the workspace + inv, root = clitest.New(t, "update", "my-workspace") + clitest.SetupConfig(t, client, root) + doneChan := make(chan struct{}) + pty := ptytest.New(t).Attach(inv) + go func() { + defer close(doneChan) + err := inv.Run() + assert.NoError(t, err) + }() + + matches := []string{ + mutableParameterName, "hello", + "Planning workspace...", "", + } + for i := 0; i < len(matches); i += 2 { + match := matches[i] + value := matches[i+1] + pty.ExpectMatch(match) + + if value != "" { + pty.WriteLine(value) + } + } + <-doneChan + }) + + t.Run("MutableRequiredParameterExists_ImmutableRequiredParameterAdded", func(t *testing.T) { + t.Parallel() + + // Create template and workspace + client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) + user := coderdtest.CreateFirstUser(t, client) + + templateParameters := []*proto.RichParameter{ + {Name: stringParameterName, Type: "string", Mutable: true, Required: true, Options: []*proto.RichParameterOption{ + {Name: "First option", Description: "This is first option", Value: "1st"}, + {Name: "Second option", Description: "This is second option", Value: "2nd"}, + {Name: "Third option", Description: "This is third option", Value: "3rd"}, + }}, + } + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, prepareEchoResponses(templateParameters)) + coderdtest.AwaitTemplateVersionJob(t, client, version.ID) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + + inv, root := clitest.New(t, "create", "my-workspace", "--yes", "--template", template.Name, "--parameter", fmt.Sprintf("%s=%s", stringParameterName, "2nd")) + clitest.SetupConfig(t, client, root) + err := inv.Run() + require.NoError(t, err) + + // Update template: add required, immutable parameter + updatedTemplateParameters := []*proto.RichParameter{ + templateParameters[0], + {Name: immutableParameterName, Type: "string", Mutable: false, Required: true, Options: []*proto.RichParameterOption{ + {Name: "fir", Description: "This is first option for immutable parameter", Value: "I"}, + {Name: "sec", Description: "This is second option for immutable parameter", Value: "II"}, + {Name: "thi", Description: "This is third option for immutable parameter", Value: "III"}, + }}, + } + + updatedVersion := coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, prepareEchoResponses(updatedTemplateParameters), template.ID) + coderdtest.AwaitTemplateVersionJob(t, client, updatedVersion.ID) + err = client.UpdateActiveTemplateVersion(context.Background(), template.ID, codersdk.UpdateActiveTemplateVersion{ + ID: updatedVersion.ID, + }) + require.NoError(t, err) + + // Update the workspace + inv, root = clitest.New(t, "update", "my-workspace") + clitest.SetupConfig(t, client, root) + doneChan := make(chan struct{}) + pty := ptytest.New(t).Attach(inv) + go func() { + defer close(doneChan) + err := inv.Run() + assert.NoError(t, err) + }() + + matches := []string{ + immutableParameterName, "thi", + "Planning workspace...", "", + } + for i := 0; i < len(matches); i += 2 { + match := matches[i] + value := matches[i+1] + pty.ExpectMatch(match) + + if value != "" { + pty.WriteLine(value) + } + } + <-doneChan + }) }