diff --git a/cli/cliui/parameter.go b/cli/cliui/parameter.go index 9b3e9e47fb..8fda0dd516 100644 --- a/cli/cliui/parameter.go +++ b/cli/cliui/parameter.go @@ -30,9 +30,15 @@ func RichParameter(inv *serpent.Invocation, templateVersionParameter codersdk.Te _, _ = fmt.Fprint(inv.Stdout, "\033[1A") var defaults []string - err = json.Unmarshal([]byte(templateVersionParameter.DefaultValue), &defaults) - if err != nil { - return "", err + defaultSource := defaultValue + if defaultSource == "" { + defaultSource = templateVersionParameter.DefaultValue + } + if defaultSource != "" { + err = json.Unmarshal([]byte(defaultSource), &defaults) + if err != nil { + return "", err + } } values, err := RichMultiSelect(inv, RichMultiSelectOptions{ diff --git a/cli/parameterresolver.go b/cli/parameterresolver.go index 9308d245f8..d443741756 100644 --- a/cli/parameterresolver.go +++ b/cli/parameterresolver.go @@ -1,6 +1,7 @@ package cli import ( + "encoding/json" "fmt" "strings" @@ -231,7 +232,7 @@ next: continue // immutables should not be passed to consecutive builds } - if len(tvp.Options) > 0 && !isValidTemplateParameterOption(buildParameter, tvp.Options) { + if len(tvp.Options) > 0 && !isValidTemplateParameterOption(buildParameter, *tvp) { continue // do not propagate invalid options } @@ -365,7 +366,7 @@ func (pr *ParameterResolver) isLastBuildParameterInvalidOption(templateVersionPa for _, buildParameter := range pr.lastBuildParameters { if buildParameter.Name == templateVersionParameter.Name { - return !isValidTemplateParameterOption(buildParameter, templateVersionParameter.Options) + return !isValidTemplateParameterOption(buildParameter, templateVersionParameter) } } return false @@ -389,8 +390,31 @@ func findWorkspaceBuildParameter(parameterName string, params []codersdk.Workspa return nil } -func isValidTemplateParameterOption(buildParameter codersdk.WorkspaceBuildParameter, options []codersdk.TemplateVersionParameterOption) bool { - for _, opt := range options { +func isValidTemplateParameterOption(buildParameter codersdk.WorkspaceBuildParameter, templateVersionParameter codersdk.TemplateVersionParameter) bool { + // Multi-select parameters store values as a JSON array (e.g. + // '["vim","emacs"]'), so we need to parse the array and validate + // each element individually against the allowed options. + if templateVersionParameter.Type == "list(string)" { + var values []string + if err := json.Unmarshal([]byte(buildParameter.Value), &values); err != nil { + return false + } + for _, v := range values { + found := false + for _, opt := range templateVersionParameter.Options { + if opt.Value == v { + found = true + break + } + } + if !found { + return false + } + } + return true + } + + for _, opt := range templateVersionParameter.Options { if opt.Value == buildParameter.Value { return true } diff --git a/cli/parameterresolver_internal_test.go b/cli/parameterresolver_internal_test.go new file mode 100644 index 0000000000..244627c58e --- /dev/null +++ b/cli/parameterresolver_internal_test.go @@ -0,0 +1,85 @@ +package cli + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/coder/coder/v2/codersdk" +) + +func TestIsValidTemplateParameterOption(t *testing.T) { + t.Parallel() + + options := []codersdk.TemplateVersionParameterOption{ + {Name: "Vim", Value: "vim"}, + {Name: "Emacs", Value: "emacs"}, + {Name: "VS Code", Value: "vscode"}, + } + + t.Run("SingleSelectValid", func(t *testing.T) { + t.Parallel() + bp := codersdk.WorkspaceBuildParameter{Name: "editor", Value: "vim"} + tvp := codersdk.TemplateVersionParameter{ + Name: "editor", + Type: "string", + Options: options, + } + assert.True(t, isValidTemplateParameterOption(bp, tvp)) + }) + + t.Run("SingleSelectInvalid", func(t *testing.T) { + t.Parallel() + bp := codersdk.WorkspaceBuildParameter{Name: "editor", Value: "notepad"} + tvp := codersdk.TemplateVersionParameter{ + Name: "editor", + Type: "string", + Options: options, + } + assert.False(t, isValidTemplateParameterOption(bp, tvp)) + }) + + t.Run("MultiSelectAllValid", func(t *testing.T) { + t.Parallel() + bp := codersdk.WorkspaceBuildParameter{Name: "editors", Value: `["vim","emacs"]`} + tvp := codersdk.TemplateVersionParameter{ + Name: "editors", + Type: "list(string)", + Options: options, + } + assert.True(t, isValidTemplateParameterOption(bp, tvp)) + }) + + t.Run("MultiSelectOneInvalid", func(t *testing.T) { + t.Parallel() + bp := codersdk.WorkspaceBuildParameter{Name: "editors", Value: `["vim","notepad"]`} + tvp := codersdk.TemplateVersionParameter{ + Name: "editors", + Type: "list(string)", + Options: options, + } + assert.False(t, isValidTemplateParameterOption(bp, tvp)) + }) + + t.Run("MultiSelectEmptyArray", func(t *testing.T) { + t.Parallel() + bp := codersdk.WorkspaceBuildParameter{Name: "editors", Value: `[]`} + tvp := codersdk.TemplateVersionParameter{ + Name: "editors", + Type: "list(string)", + Options: options, + } + assert.True(t, isValidTemplateParameterOption(bp, tvp)) + }) + + t.Run("MultiSelectInvalidJSON", func(t *testing.T) { + t.Parallel() + bp := codersdk.WorkspaceBuildParameter{Name: "editors", Value: `not-json`} + tvp := codersdk.TemplateVersionParameter{ + Name: "editors", + Type: "list(string)", + Options: options, + } + assert.False(t, isValidTemplateParameterOption(bp, tvp)) + }) +}