mirror of https://github.com/coder/coder.git
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
This commit is contained in:
parent
053c56cc1a
commit
0e3dc2a80f
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
18
cli/start.go
18
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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -91,3 +91,12 @@ Rich parameter value in the format "name=value".
|
|||
| Environment | <code>$CODER_RICH_PARAMETER_FILE</code> |
|
||||
|
||||
Specify a file path with values for rich parameters defined in the template.
|
||||
|
||||
### --parameter-default
|
||||
|
||||
| | |
|
||||
| ----------- | ------------------------------------------ |
|
||||
| Type | <code>string-array</code> |
|
||||
| Environment | <code>$CODER_RICH_PARAMETER_DEFAULT</code> |
|
||||
|
||||
Rich parameter default values in the format "name=value".
|
||||
|
|
|
@ -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 | <code>string-array</code> |
|
||||
| Environment | <code>$CODER_RICH_PARAMETER_DEFAULT</code> |
|
||||
|
||||
Rich parameter default values in the format "name=value".
|
||||
|
||||
### --always-prompt
|
||||
|
||||
| | |
|
||||
|
|
|
@ -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 | <code>string-array</code> |
|
||||
| Environment | <code>$CODER_RICH_PARAMETER_DEFAULT</code> |
|
||||
|
||||
Rich parameter default values in the format "name=value".
|
||||
|
||||
### --always-prompt
|
||||
|
||||
| | |
|
||||
|
|
|
@ -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 | <code>string-array</code> |
|
||||
| Environment | <code>$CODER_RICH_PARAMETER_DEFAULT</code> |
|
||||
|
||||
Rich parameter default values in the format "name=value".
|
||||
|
||||
### --always-prompt
|
||||
|
||||
| | |
|
||||
|
|
Loading…
Reference in New Issue