mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
fix(cli): reuse multi-select parameter values on workspace update (#22261)
Fixes three bugs that caused `coder update` to always re-prompt for multi-select (`list(string)`) parameters instead of reusing previous build values: 1. **`isValidTemplateParameterOption` failed for multi-select values** (`cli/parameterresolver.go`): It compared the entire JSON array string (e.g. `["vim","emacs"]`) against individual option values, which never matched. Now parses the JSON array and validates each element separately. 2. **`RichParameter` ignored previous build value for multi-select** (`cli/cliui/parameter.go`): The `list(string)` branch always used the template's default value instead of the `defaultValue` argument (which carries the previous build's value). Now uses `defaultValue` when available, falling back to the template default. 3. **Pre-existing crash when `list(string)` has no default value** (`cli/cliui/parameter.go`): `json.Unmarshal` on an empty string caused `unexpected end of JSON input`. Now skips unmarshaling when the default source is empty. Fixes #19956
This commit is contained in:
@@ -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{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user