Skip to content

Commit 0e3dc2a

Browse files
authored
feat: influence parameter defaults through cli flag/env (#13039)
* feat: influence parameter defaults through cli flag/env Add a --parameter-default flag / CODER_RICH_PARAMETER_DEFAULT environment variable which overrides default values suggested for parameters. This allows scripts or middleware wrapping the CLI to substitute defaults for parameter values beyond those defined at the template level. For example, Git repository/branch parameters can be given defaults based on the current checkout, or default parameter values can be parsed out of files inside the repo. * Rename defaults arg to defaultOverrides
1 parent 053c56c commit 0e3dc2a

14 files changed

+176
-22
lines changed

cli/cliui/parameter.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
"github.com/coder/serpent"
1111
)
1212

13-
func RichParameter(inv *serpent.Invocation, templateVersionParameter codersdk.TemplateVersionParameter) (string, error) {
13+
func RichParameter(inv *serpent.Invocation, templateVersionParameter codersdk.TemplateVersionParameter, defaultOverrides map[string]string) (string, error) {
1414
label := templateVersionParameter.Name
1515
if templateVersionParameter.DisplayName != "" {
1616
label = templateVersionParameter.DisplayName
@@ -26,6 +26,11 @@ func RichParameter(inv *serpent.Invocation, templateVersionParameter codersdk.Te
2626
_, _ = fmt.Fprintln(inv.Stdout, " "+strings.TrimSpace(strings.Join(strings.Split(templateVersionParameter.DescriptionPlaintext, "\n"), "\n "))+"\n")
2727
}
2828

29+
defaultValue := templateVersionParameter.DefaultValue
30+
if v, ok := defaultOverrides[templateVersionParameter.Name]; ok {
31+
defaultValue = v
32+
}
33+
2934
var err error
3035
var value string
3136
if templateVersionParameter.Type == "list(string)" {
@@ -58,7 +63,7 @@ func RichParameter(inv *serpent.Invocation, templateVersionParameter codersdk.Te
5863
var richParameterOption *codersdk.TemplateVersionParameterOption
5964
richParameterOption, err = RichSelect(inv, RichSelectOptions{
6065
Options: templateVersionParameter.Options,
61-
Default: templateVersionParameter.DefaultValue,
66+
Default: defaultValue,
6267
HideSearch: true,
6368
})
6469
if err == nil {
@@ -69,7 +74,7 @@ func RichParameter(inv *serpent.Invocation, templateVersionParameter codersdk.Te
6974
} else {
7075
text := "Enter a value"
7176
if !templateVersionParameter.Required {
72-
text += fmt.Sprintf(" (default: %q)", templateVersionParameter.DefaultValue)
77+
text += fmt.Sprintf(" (default: %q)", defaultValue)
7378
}
7479
text += ":"
7580

@@ -87,7 +92,7 @@ func RichParameter(inv *serpent.Invocation, templateVersionParameter codersdk.Te
8792

8893
// If they didn't specify anything, use the default value if set.
8994
if len(templateVersionParameter.Options) == 0 && value == "" {
90-
value = templateVersionParameter.DefaultValue
95+
value = defaultValue
9196
}
9297

9398
return value, nil

cli/create.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,11 @@ func (r *RootCmd) create() *serpent.Command {
165165
return xerrors.Errorf("can't parse given parameter values: %w", err)
166166
}
167167

168+
cliBuildParameterDefaults, err := asWorkspaceBuildParameters(parameterFlags.richParameterDefaults)
169+
if err != nil {
170+
return xerrors.Errorf("can't parse given parameter defaults: %w", err)
171+
}
172+
168173
var sourceWorkspaceParameters []codersdk.WorkspaceBuildParameter
169174
if copyParametersFrom != "" {
170175
sourceWorkspaceParameters, err = client.WorkspaceBuildParameters(inv.Context(), sourceWorkspace.LatestBuild.ID)
@@ -178,8 +183,9 @@ func (r *RootCmd) create() *serpent.Command {
178183
TemplateVersionID: templateVersionID,
179184
NewWorkspaceName: workspaceName,
180185

181-
RichParameterFile: parameterFlags.richParameterFile,
182-
RichParameters: cliBuildParameters,
186+
RichParameterFile: parameterFlags.richParameterFile,
187+
RichParameters: cliBuildParameters,
188+
RichParameterDefaults: cliBuildParameterDefaults,
183189

184190
SourceWorkspaceParameters: sourceWorkspaceParameters,
185191
})
@@ -262,6 +268,7 @@ func (r *RootCmd) create() *serpent.Command {
262268
cliui.SkipPromptOption(),
263269
)
264270
cmd.Options = append(cmd.Options, parameterFlags.cliParameters()...)
271+
cmd.Options = append(cmd.Options, parameterFlags.cliParameterDefaults()...)
265272
return cmd
266273
}
267274

@@ -276,9 +283,10 @@ type prepWorkspaceBuildArgs struct {
276283
PromptBuildOptions bool
277284
BuildOptions []codersdk.WorkspaceBuildParameter
278285

279-
PromptRichParameters bool
280-
RichParameters []codersdk.WorkspaceBuildParameter
281-
RichParameterFile string
286+
PromptRichParameters bool
287+
RichParameters []codersdk.WorkspaceBuildParameter
288+
RichParameterFile string
289+
RichParameterDefaults []codersdk.WorkspaceBuildParameter
282290
}
283291

284292
// 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
311319
WithBuildOptions(args.BuildOptions).
312320
WithPromptRichParameters(args.PromptRichParameters).
313321
WithRichParameters(args.RichParameters).
314-
WithRichParametersFile(parameterFile)
322+
WithRichParametersFile(parameterFile).
323+
WithRichParametersDefaults(args.RichParameterDefaults)
315324
buildParameters, err := resolver.Resolve(inv, args.Action, templateVersionParameters)
316325
if err != nil {
317326
return nil, err

cli/create_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,68 @@ func TestCreateWithRichParameters(t *testing.T) {
315315
<-doneChan
316316
})
317317

318+
t.Run("ParametersDefaults", func(t *testing.T) {
319+
t.Parallel()
320+
321+
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
322+
owner := coderdtest.CreateFirstUser(t, client)
323+
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
324+
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, echoResponses)
325+
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
326+
327+
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
328+
329+
inv, root := clitest.New(t, "create", "my-workspace", "--template", template.Name,
330+
"--parameter-default", fmt.Sprintf("%s=%s", firstParameterName, firstParameterValue),
331+
"--parameter-default", fmt.Sprintf("%s=%s", secondParameterName, secondParameterValue),
332+
"--parameter-default", fmt.Sprintf("%s=%s", immutableParameterName, immutableParameterValue))
333+
clitest.SetupConfig(t, member, root)
334+
doneChan := make(chan struct{})
335+
pty := ptytest.New(t).Attach(inv)
336+
go func() {
337+
defer close(doneChan)
338+
err := inv.Run()
339+
assert.NoError(t, err)
340+
}()
341+
342+
matches := []string{
343+
firstParameterDescription, firstParameterValue,
344+
secondParameterDescription, secondParameterValue,
345+
immutableParameterDescription, immutableParameterValue,
346+
}
347+
for i := 0; i < len(matches); i += 2 {
348+
match := matches[i]
349+
defaultValue := matches[i+1]
350+
351+
pty.ExpectMatch(match)
352+
pty.ExpectMatch(`Enter a value (default: "` + defaultValue + `")`)
353+
pty.WriteLine("")
354+
}
355+
pty.ExpectMatch("Confirm create?")
356+
pty.WriteLine("yes")
357+
<-doneChan
358+
359+
// Verify that the expected default values were used.
360+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
361+
defer cancel()
362+
363+
workspaces, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{
364+
Name: "my-workspace",
365+
})
366+
require.NoError(t, err, "can't list available workspaces")
367+
require.Len(t, workspaces.Workspaces, 1)
368+
369+
workspaceLatestBuild := workspaces.Workspaces[0].LatestBuild
370+
require.Equal(t, version.ID, workspaceLatestBuild.TemplateVersionID)
371+
372+
buildParameters, err := client.WorkspaceBuildParameters(ctx, workspaceLatestBuild.ID)
373+
require.NoError(t, err)
374+
require.Len(t, buildParameters, 3)
375+
require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: firstParameterName, Value: firstParameterValue})
376+
require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: secondParameterName, Value: secondParameterValue})
377+
require.Contains(t, buildParameters, codersdk.WorkspaceBuildParameter{Name: immutableParameterName, Value: immutableParameterValue})
378+
})
379+
318380
t.Run("RichParametersFile", func(t *testing.T) {
319381
t.Parallel()
320382

cli/parameter.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,16 @@ type workspaceParameterFlags struct {
1818
promptBuildOptions bool
1919
buildOptions []string
2020

21-
richParameterFile string
22-
richParameters []string
21+
richParameterFile string
22+
richParameters []string
23+
richParameterDefaults []string
2324

2425
promptRichParameters bool
2526
}
2627

2728
func (wpf *workspaceParameterFlags) allOptions() []serpent.Option {
2829
options := append(wpf.cliBuildOptions(), wpf.cliParameters()...)
30+
options = append(options, wpf.cliParameterDefaults()...)
2931
return append(options, wpf.alwaysPrompt())
3032
}
3133

@@ -62,6 +64,17 @@ func (wpf *workspaceParameterFlags) cliParameters() []serpent.Option {
6264
}
6365
}
6466

67+
func (wpf *workspaceParameterFlags) cliParameterDefaults() []serpent.Option {
68+
return serpent.OptionSet{
69+
serpent.Option{
70+
Flag: "parameter-default",
71+
Env: "CODER_RICH_PARAMETER_DEFAULT",
72+
Description: `Rich parameter default values in the format "name=value".`,
73+
Value: serpent.StringArrayOf(&wpf.richParameterDefaults),
74+
},
75+
}
76+
}
77+
6578
func (wpf *workspaceParameterFlags) alwaysPrompt() serpent.Option {
6679
return serpent.Option{
6780
Flag: "always-prompt",

cli/parameterresolver.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@ type ParameterResolver struct {
2626
lastBuildParameters []codersdk.WorkspaceBuildParameter
2727
sourceWorkspaceParameters []codersdk.WorkspaceBuildParameter
2828

29-
richParameters []codersdk.WorkspaceBuildParameter
30-
richParametersFile map[string]string
31-
buildOptions []codersdk.WorkspaceBuildParameter
29+
richParameters []codersdk.WorkspaceBuildParameter
30+
richParametersDefaults map[string]string
31+
richParametersFile map[string]string
32+
buildOptions []codersdk.WorkspaceBuildParameter
3233

3334
promptRichParameters bool
3435
promptBuildOptions bool
@@ -59,6 +60,16 @@ func (pr *ParameterResolver) WithRichParametersFile(fileMap map[string]string) *
5960
return pr
6061
}
6162

63+
func (pr *ParameterResolver) WithRichParametersDefaults(params []codersdk.WorkspaceBuildParameter) *ParameterResolver {
64+
if pr.richParametersDefaults == nil {
65+
pr.richParametersDefaults = make(map[string]string)
66+
}
67+
for _, p := range params {
68+
pr.richParametersDefaults[p.Name] = p.Value
69+
}
70+
return pr
71+
}
72+
6273
func (pr *ParameterResolver) WithPromptRichParameters(promptRichParameters bool) *ParameterResolver {
6374
pr.promptRichParameters = promptRichParameters
6475
return pr
@@ -227,7 +238,7 @@ func (pr *ParameterResolver) resolveWithInput(resolved []codersdk.WorkspaceBuild
227238
(action == WorkspaceUpdate && tvp.Mutable && tvp.Required) ||
228239
(action == WorkspaceUpdate && !tvp.Mutable && firstTimeUse) ||
229240
(tvp.Mutable && !tvp.Ephemeral && pr.promptRichParameters) {
230-
parameterValue, err := cliui.RichParameter(inv, tvp)
241+
parameterValue, err := cliui.RichParameter(inv, tvp, pr.richParametersDefaults)
231242
if err != nil {
232243
return nil, err
233244
}

cli/start.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,12 @@ func buildWorkspaceStartRequest(inv *serpent.Invocation, client *codersdk.Client
9999

100100
cliRichParameters, err := asWorkspaceBuildParameters(parameterFlags.richParameters)
101101
if err != nil {
102-
return codersdk.CreateWorkspaceBuildRequest{}, xerrors.Errorf("unable to parse build options: %w", err)
102+
return codersdk.CreateWorkspaceBuildRequest{}, xerrors.Errorf("unable to parse rich parameters: %w", err)
103+
}
104+
105+
cliRichParameterDefaults, err := asWorkspaceBuildParameters(parameterFlags.richParameterDefaults)
106+
if err != nil {
107+
return codersdk.CreateWorkspaceBuildRequest{}, xerrors.Errorf("unable to parse rich parameter defaults: %w", err)
103108
}
104109

105110
buildParameters, err := prepWorkspaceBuild(inv, client, prepWorkspaceBuildArgs{
@@ -108,11 +113,12 @@ func buildWorkspaceStartRequest(inv *serpent.Invocation, client *codersdk.Client
108113
NewWorkspaceName: workspace.Name,
109114
LastBuildParameters: lastBuildParameters,
110115

111-
PromptBuildOptions: parameterFlags.promptBuildOptions,
112-
BuildOptions: buildOptions,
113-
PromptRichParameters: parameterFlags.promptRichParameters,
114-
RichParameters: cliRichParameters,
115-
RichParameterFile: parameterFlags.richParameterFile,
116+
PromptBuildOptions: parameterFlags.promptBuildOptions,
117+
BuildOptions: buildOptions,
118+
PromptRichParameters: parameterFlags.promptRichParameters,
119+
RichParameters: cliRichParameters,
120+
RichParameterFile: parameterFlags.richParameterFile,
121+
RichParameterDefaults: cliRichParameterDefaults,
116122
})
117123
if err != nil {
118124
return codersdk.CreateWorkspaceBuildRequest{}, err

cli/testdata/coder_create_--help.golden

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ OPTIONS:
2020
--parameter string-array, $CODER_RICH_PARAMETER
2121
Rich parameter value in the format "name=value".
2222

23+
--parameter-default string-array, $CODER_RICH_PARAMETER_DEFAULT
24+
Rich parameter default values in the format "name=value".
25+
2326
--rich-parameter-file string, $CODER_RICH_PARAMETER_FILE
2427
Specify a file path with values for rich parameters defined in the
2528
template.

cli/testdata/coder_restart_--help.golden

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ OPTIONS:
1919
--parameter string-array, $CODER_RICH_PARAMETER
2020
Rich parameter value in the format "name=value".
2121

22+
--parameter-default string-array, $CODER_RICH_PARAMETER_DEFAULT
23+
Rich parameter default values in the format "name=value".
24+
2225
--rich-parameter-file string, $CODER_RICH_PARAMETER_FILE
2326
Specify a file path with values for rich parameters defined in the
2427
template.

cli/testdata/coder_start_--help.golden

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ OPTIONS:
1919
--parameter string-array, $CODER_RICH_PARAMETER
2020
Rich parameter value in the format "name=value".
2121

22+
--parameter-default string-array, $CODER_RICH_PARAMETER_DEFAULT
23+
Rich parameter default values in the format "name=value".
24+
2225
--rich-parameter-file string, $CODER_RICH_PARAMETER_FILE
2326
Specify a file path with values for rich parameters defined in the
2427
template.

cli/testdata/coder_update_--help.golden

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ OPTIONS:
2121
--parameter string-array, $CODER_RICH_PARAMETER
2222
Rich parameter value in the format "name=value".
2323

24+
--parameter-default string-array, $CODER_RICH_PARAMETER_DEFAULT
25+
Rich parameter default values in the format "name=value".
26+
2427
--rich-parameter-file string, $CODER_RICH_PARAMETER_FILE
2528
Specify a file path with values for rich parameters defined in the
2629
template.

0 commit comments

Comments
 (0)