diff --git a/cli/cliui/parameter.go b/cli/cliui/parameter.go index 789ce0c033..897ddec4de 100644 --- a/cli/cliui/parameter.go +++ b/cli/cliui/parameter.go @@ -10,7 +10,7 @@ import ( "github.com/coder/serpent" ) -func RichParameter(inv *serpent.Invocation, templateVersionParameter codersdk.TemplateVersionParameter) (string, error) { +func RichParameter(inv *serpent.Invocation, templateVersionParameter codersdk.TemplateVersionParameter, defaultOverrides map[string]string) (string, error) { label := templateVersionParameter.Name if templateVersionParameter.DisplayName != "" { label = templateVersionParameter.DisplayName @@ -26,6 +26,11 @@ func RichParameter(inv *serpent.Invocation, templateVersionParameter codersdk.Te _, _ = fmt.Fprintln(inv.Stdout, " "+strings.TrimSpace(strings.Join(strings.Split(templateVersionParameter.DescriptionPlaintext, "\n"), "\n "))+"\n") } + defaultValue := templateVersionParameter.DefaultValue + if v, ok := defaultOverrides[templateVersionParameter.Name]; ok { + defaultValue = v + } + var err error var value string if templateVersionParameter.Type == "list(string)" { @@ -58,7 +63,7 @@ func RichParameter(inv *serpent.Invocation, templateVersionParameter codersdk.Te var richParameterOption *codersdk.TemplateVersionParameterOption richParameterOption, err = RichSelect(inv, RichSelectOptions{ Options: templateVersionParameter.Options, - Default: templateVersionParameter.DefaultValue, + Default: defaultValue, HideSearch: true, }) if err == nil { @@ -69,7 +74,7 @@ func RichParameter(inv *serpent.Invocation, templateVersionParameter codersdk.Te } else { text := "Enter a value" if !templateVersionParameter.Required { - text += fmt.Sprintf(" (default: %q)", templateVersionParameter.DefaultValue) + text += fmt.Sprintf(" (default: %q)", defaultValue) } text += ":" @@ -87,7 +92,7 @@ func RichParameter(inv *serpent.Invocation, templateVersionParameter codersdk.Te // If they didn't specify anything, use the default value if set. if len(templateVersionParameter.Options) == 0 && value == "" { - value = templateVersionParameter.DefaultValue + value = defaultValue } return value, nil diff --git a/cli/create.go b/cli/create.go index 4aff56992d..ab9a204fad 100644 --- a/cli/create.go +++ b/cli/create.go @@ -165,6 +165,11 @@ func (r *RootCmd) create() *serpent.Command { return xerrors.Errorf("can't parse given parameter values: %w", err) } + cliBuildParameterDefaults, err := asWorkspaceBuildParameters(parameterFlags.richParameterDefaults) + if err != nil { + return xerrors.Errorf("can't parse given parameter defaults: %w", err) + } + var sourceWorkspaceParameters []codersdk.WorkspaceBuildParameter if copyParametersFrom != "" { sourceWorkspaceParameters, err = client.WorkspaceBuildParameters(inv.Context(), sourceWorkspace.LatestBuild.ID) @@ -178,8 +183,9 @@ func (r *RootCmd) create() *serpent.Command { TemplateVersionID: templateVersionID, NewWorkspaceName: workspaceName, - RichParameterFile: parameterFlags.richParameterFile, - RichParameters: cliBuildParameters, + RichParameterFile: parameterFlags.richParameterFile, + RichParameters: cliBuildParameters, + RichParameterDefaults: cliBuildParameterDefaults, SourceWorkspaceParameters: sourceWorkspaceParameters, }) @@ -262,6 +268,7 @@ func (r *RootCmd) create() *serpent.Command { cliui.SkipPromptOption(), ) cmd.Options = append(cmd.Options, parameterFlags.cliParameters()...) + cmd.Options = append(cmd.Options, parameterFlags.cliParameterDefaults()...) return cmd } @@ -276,9 +283,10 @@ type prepWorkspaceBuildArgs struct { PromptBuildOptions bool BuildOptions []codersdk.WorkspaceBuildParameter - PromptRichParameters bool - RichParameters []codersdk.WorkspaceBuildParameter - RichParameterFile string + PromptRichParameters bool + RichParameters []codersdk.WorkspaceBuildParameter + RichParameterFile string + RichParameterDefaults []codersdk.WorkspaceBuildParameter } // prepWorkspaceBuild will ensure a workspace build will succeed on the latest template version. @@ -311,7 +319,8 @@ func prepWorkspaceBuild(inv *serpent.Invocation, client *codersdk.Client, args p WithBuildOptions(args.BuildOptions). WithPromptRichParameters(args.PromptRichParameters). WithRichParameters(args.RichParameters). - WithRichParametersFile(parameterFile) + WithRichParametersFile(parameterFile). + WithRichParametersDefaults(args.RichParameterDefaults) buildParameters, err := resolver.Resolve(inv, args.Action, templateVersionParameters) if err != nil { return nil, err diff --git a/cli/create_test.go b/cli/create_test.go index 639f23c2e7..b6294f50b4 100644 --- a/cli/create_test.go +++ b/cli/create_test.go @@ -315,6 +315,68 @@ func TestCreateWithRichParameters(t *testing.T) { <-doneChan }) + t.Run("ParametersDefaults", func(t *testing.T) { + t.Parallel() + + client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) + owner := coderdtest.CreateFirstUser(t, client) + member, _ := 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) + + inv, root := clitest.New(t, "create", "my-workspace", "--template", template.Name, + "--parameter-default", fmt.Sprintf("%s=%s", firstParameterName, firstParameterValue), + "--parameter-default", fmt.Sprintf("%s=%s", secondParameterName, secondParameterValue), + "--parameter-default", fmt.Sprintf("%s=%s", immutableParameterName, immutableParameterValue)) + clitest.SetupConfig(t, member, 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{ + firstParameterDescription, firstParameterValue, + secondParameterDescription, secondParameterValue, + immutableParameterDescription, immutableParameterValue, + } + for i := 0; i < len(matches); i += 2 { + match := matches[i] + defaultValue := matches[i+1] + + pty.ExpectMatch(match) + pty.ExpectMatch(`Enter a value (default: "` + defaultValue + `")`) + pty.WriteLine("") + } + pty.ExpectMatch("Confirm create?") + pty.WriteLine("yes") + <-doneChan + + // Verify that the expected default values were used. + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) + defer cancel() + + workspaces, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{ + Name: "my-workspace", + }) + require.NoError(t, err, "can't list available workspaces") + require.Len(t, workspaces.Workspaces, 1) + + workspaceLatestBuild := workspaces.Workspaces[0].LatestBuild + require.Equal(t, version.ID, workspaceLatestBuild.TemplateVersionID) + + buildParameters, err := client.WorkspaceBuildParameters(ctx, workspaceLatestBuild.ID) + require.NoError(t, err) + require.Len(t, buildParameters, 3) + require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: firstParameterName, Value: firstParameterValue}) + require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: secondParameterName, Value: secondParameterValue}) + require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: immutableParameterName, Value: immutableParameterValue}) + }) + t.Run("RichParametersFile", func(t *testing.T) { t.Parallel() diff --git a/cli/parameter.go b/cli/parameter.go index 0070c47e5b..e9b1ca581a 100644 --- a/cli/parameter.go +++ b/cli/parameter.go @@ -18,14 +18,16 @@ type workspaceParameterFlags struct { promptBuildOptions bool buildOptions []string - richParameterFile string - richParameters []string + richParameterFile string + richParameters []string + richParameterDefaults []string promptRichParameters bool } func (wpf *workspaceParameterFlags) allOptions() []serpent.Option { options := append(wpf.cliBuildOptions(), wpf.cliParameters()...) + options = append(options, wpf.cliParameterDefaults()...) return append(options, wpf.alwaysPrompt()) } @@ -62,6 +64,17 @@ func (wpf *workspaceParameterFlags) cliParameters() []serpent.Option { } } +func (wpf *workspaceParameterFlags) cliParameterDefaults() []serpent.Option { + return serpent.OptionSet{ + serpent.Option{ + Flag: "parameter-default", + Env: "CODER_RICH_PARAMETER_DEFAULT", + Description: `Rich parameter default values in the format "name=value".`, + Value: serpent.StringArrayOf(&wpf.richParameterDefaults), + }, + } +} + func (wpf *workspaceParameterFlags) alwaysPrompt() serpent.Option { return serpent.Option{ Flag: "always-prompt", diff --git a/cli/parameterresolver.go b/cli/parameterresolver.go index 3e1e70d1cc..437b4bd407 100644 --- a/cli/parameterresolver.go +++ b/cli/parameterresolver.go @@ -26,9 +26,10 @@ type ParameterResolver struct { lastBuildParameters []codersdk.WorkspaceBuildParameter sourceWorkspaceParameters []codersdk.WorkspaceBuildParameter - richParameters []codersdk.WorkspaceBuildParameter - richParametersFile map[string]string - buildOptions []codersdk.WorkspaceBuildParameter + richParameters []codersdk.WorkspaceBuildParameter + richParametersDefaults map[string]string + richParametersFile map[string]string + buildOptions []codersdk.WorkspaceBuildParameter promptRichParameters bool promptBuildOptions bool @@ -59,6 +60,16 @@ func (pr *ParameterResolver) WithRichParametersFile(fileMap map[string]string) * return pr } +func (pr *ParameterResolver) WithRichParametersDefaults(params []codersdk.WorkspaceBuildParameter) *ParameterResolver { + if pr.richParametersDefaults == nil { + pr.richParametersDefaults = make(map[string]string) + } + for _, p := range params { + pr.richParametersDefaults[p.Name] = p.Value + } + return pr +} + func (pr *ParameterResolver) WithPromptRichParameters(promptRichParameters bool) *ParameterResolver { pr.promptRichParameters = promptRichParameters return pr @@ -227,7 +238,7 @@ func (pr *ParameterResolver) resolveWithInput(resolved []codersdk.WorkspaceBuild (action == WorkspaceUpdate && tvp.Mutable && tvp.Required) || (action == WorkspaceUpdate && !tvp.Mutable && firstTimeUse) || (tvp.Mutable && !tvp.Ephemeral && pr.promptRichParameters) { - parameterValue, err := cliui.RichParameter(inv, tvp) + parameterValue, err := cliui.RichParameter(inv, tvp, pr.richParametersDefaults) if err != nil { return nil, err } diff --git a/cli/start.go b/cli/start.go index b0f0a4be4b..89f15b95c4 100644 --- a/cli/start.go +++ b/cli/start.go @@ -99,7 +99,12 @@ func buildWorkspaceStartRequest(inv *serpent.Invocation, client *codersdk.Client cliRichParameters, err := asWorkspaceBuildParameters(parameterFlags.richParameters) if err != nil { - return codersdk.CreateWorkspaceBuildRequest{}, xerrors.Errorf("unable to parse build options: %w", err) + return codersdk.CreateWorkspaceBuildRequest{}, xerrors.Errorf("unable to parse rich parameters: %w", err) + } + + cliRichParameterDefaults, err := asWorkspaceBuildParameters(parameterFlags.richParameterDefaults) + if err != nil { + return codersdk.CreateWorkspaceBuildRequest{}, xerrors.Errorf("unable to parse rich parameter defaults: %w", err) } buildParameters, err := prepWorkspaceBuild(inv, client, prepWorkspaceBuildArgs{ @@ -108,11 +113,12 @@ func buildWorkspaceStartRequest(inv *serpent.Invocation, client *codersdk.Client NewWorkspaceName: workspace.Name, LastBuildParameters: lastBuildParameters, - PromptBuildOptions: parameterFlags.promptBuildOptions, - BuildOptions: buildOptions, - PromptRichParameters: parameterFlags.promptRichParameters, - RichParameters: cliRichParameters, - RichParameterFile: parameterFlags.richParameterFile, + PromptBuildOptions: parameterFlags.promptBuildOptions, + BuildOptions: buildOptions, + PromptRichParameters: parameterFlags.promptRichParameters, + RichParameters: cliRichParameters, + RichParameterFile: parameterFlags.richParameterFile, + RichParameterDefaults: cliRichParameterDefaults, }) if err != nil { return codersdk.CreateWorkspaceBuildRequest{}, err diff --git a/cli/testdata/coder_create_--help.golden b/cli/testdata/coder_create_--help.golden index 7662ecd7ed..9edadd5500 100644 --- a/cli/testdata/coder_create_--help.golden +++ b/cli/testdata/coder_create_--help.golden @@ -20,6 +20,9 @@ OPTIONS: --parameter string-array, $CODER_RICH_PARAMETER Rich parameter value in the format "name=value". + --parameter-default string-array, $CODER_RICH_PARAMETER_DEFAULT + Rich parameter default values in the format "name=value". + --rich-parameter-file string, $CODER_RICH_PARAMETER_FILE Specify a file path with values for rich parameters defined in the template. diff --git a/cli/testdata/coder_restart_--help.golden b/cli/testdata/coder_restart_--help.golden index b274812f7f..b0b036929c 100644 --- a/cli/testdata/coder_restart_--help.golden +++ b/cli/testdata/coder_restart_--help.golden @@ -19,6 +19,9 @@ OPTIONS: --parameter string-array, $CODER_RICH_PARAMETER Rich parameter value in the format "name=value". + --parameter-default string-array, $CODER_RICH_PARAMETER_DEFAULT + Rich parameter default values in the format "name=value". + --rich-parameter-file string, $CODER_RICH_PARAMETER_FILE Specify a file path with values for rich parameters defined in the template. diff --git a/cli/testdata/coder_start_--help.golden b/cli/testdata/coder_start_--help.golden index 10df0d5bc4..4985930b62 100644 --- a/cli/testdata/coder_start_--help.golden +++ b/cli/testdata/coder_start_--help.golden @@ -19,6 +19,9 @@ OPTIONS: --parameter string-array, $CODER_RICH_PARAMETER Rich parameter value in the format "name=value". + --parameter-default string-array, $CODER_RICH_PARAMETER_DEFAULT + Rich parameter default values in the format "name=value". + --rich-parameter-file string, $CODER_RICH_PARAMETER_FILE Specify a file path with values for rich parameters defined in the template. diff --git a/cli/testdata/coder_update_--help.golden b/cli/testdata/coder_update_--help.golden index 896747ca7f..bff9086846 100644 --- a/cli/testdata/coder_update_--help.golden +++ b/cli/testdata/coder_update_--help.golden @@ -21,6 +21,9 @@ OPTIONS: --parameter string-array, $CODER_RICH_PARAMETER Rich parameter value in the format "name=value". + --parameter-default string-array, $CODER_RICH_PARAMETER_DEFAULT + Rich parameter default values in the format "name=value". + --rich-parameter-file string, $CODER_RICH_PARAMETER_FILE Specify a file path with values for rich parameters defined in the template. diff --git a/docs/cli/create.md b/docs/cli/create.md index 8eeb90ebbe..53f9075151 100644 --- a/docs/cli/create.md +++ b/docs/cli/create.md @@ -91,3 +91,12 @@ Rich parameter value in the format "name=value". | Environment | $CODER_RICH_PARAMETER_FILE | Specify a file path with values for rich parameters defined in the template. + +### --parameter-default + +| | | +| ----------- | ------------------------------------------ | +| Type | string-array | +| Environment | $CODER_RICH_PARAMETER_DEFAULT | + +Rich parameter default values in the format "name=value". diff --git a/docs/cli/restart.md b/docs/cli/restart.md index a2eb033e80..215917a8e0 100644 --- a/docs/cli/restart.md +++ b/docs/cli/restart.md @@ -55,6 +55,15 @@ Rich parameter value in the format "name=value". Specify a file path with values for rich parameters defined in the template. +### --parameter-default + +| | | +| ----------- | ------------------------------------------ | +| Type | string-array | +| Environment | $CODER_RICH_PARAMETER_DEFAULT | + +Rich parameter default values in the format "name=value". + ### --always-prompt | | | diff --git a/docs/cli/start.md b/docs/cli/start.md index 44796fa817..0852ec5b57 100644 --- a/docs/cli/start.md +++ b/docs/cli/start.md @@ -55,6 +55,15 @@ Rich parameter value in the format "name=value". Specify a file path with values for rich parameters defined in the template. +### --parameter-default + +| | | +| ----------- | ------------------------------------------ | +| Type | string-array | +| Environment | $CODER_RICH_PARAMETER_DEFAULT | + +Rich parameter default values in the format "name=value". + ### --always-prompt | | | diff --git a/docs/cli/update.md b/docs/cli/update.md index 89d6693d0c..562d32f8fd 100644 --- a/docs/cli/update.md +++ b/docs/cli/update.md @@ -53,6 +53,15 @@ Rich parameter value in the format "name=value". Specify a file path with values for rich parameters defined in the template. +### --parameter-default + +| | | +| ----------- | ------------------------------------------ | +| Type | string-array | +| Environment | $CODER_RICH_PARAMETER_DEFAULT | + +Rich parameter default values in the format "name=value". + ### --always-prompt | | |