chore: add form_type parameter argument to db (#17920)

`form_type` is a new parameter field in the terraform provider. Bring
that field into coder/coder.

Validation for `multi-select` has also been added.
This commit is contained in:
Steven Masley
2025-05-29 08:55:19 -05:00
committed by GitHub
parent 776c144128
commit 8387dd27ab
35 changed files with 1556 additions and 822 deletions
+36 -12
View File
@@ -1,9 +1,12 @@
package codersdk
import (
"encoding/json"
"golang.org/x/xerrors"
"tailscale.com/types/ptr"
"github.com/coder/coder/v2/coderd/util/slice"
"github.com/coder/terraform-provider-coder/v2/provider"
)
@@ -66,18 +69,8 @@ func validateBuildParameter(richParameter TemplateVersionParameter, buildParamet
current = richParameter.DefaultValue
}
if len(richParameter.Options) > 0 {
var matched bool
for _, opt := range richParameter.Options {
if opt.Value == current {
matched = true
break
}
}
if !matched {
return xerrors.Errorf("parameter value must match one of options: %s", parameterValuesAsArray(richParameter.Options))
}
if len(richParameter.Options) > 0 && !inOptionSet(richParameter, current) {
return xerrors.Errorf("parameter value must match one of options: %s", parameterValuesAsArray(richParameter.Options))
}
if !validationEnabled(richParameter) {
@@ -104,6 +97,37 @@ func validateBuildParameter(richParameter TemplateVersionParameter, buildParamet
return validation.Valid(richParameter.Type, current, previous)
}
// inOptionSet returns if the value given is in the set of options for a parameter.
func inOptionSet(richParameter TemplateVersionParameter, value string) bool {
optionValues := make([]string, 0, len(richParameter.Options))
for _, option := range richParameter.Options {
optionValues = append(optionValues, option.Value)
}
// If the type is `list(string)` and the form_type is `multi-select`, then we check each individual
// value in the list against the option set.
isMultiSelect := richParameter.Type == provider.OptionTypeListString && richParameter.FormType == string(provider.ParameterFormTypeMultiSelect)
if !isMultiSelect {
// This is the simple case. Just checking if the value is in the option set.
return slice.Contains(optionValues, value)
}
var checks []string
err := json.Unmarshal([]byte(value), &checks)
if err != nil {
return false
}
for _, check := range checks {
if !slice.Contains(optionValues, check) {
return false
}
}
return true
}
func findBuildParameter(params []WorkspaceBuildParameter, parameterName string) (*WorkspaceBuildParameter, bool) {
if params == nil {
return nil, false
+149
View File
@@ -0,0 +1,149 @@
package codersdk
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/terraform-provider-coder/v2/provider"
)
func Test_inOptionSet(t *testing.T) {
t.Parallel()
options := func(vals ...string) []TemplateVersionParameterOption {
opts := make([]TemplateVersionParameterOption, 0, len(vals))
for _, val := range vals {
opts = append(opts, TemplateVersionParameterOption{
Name: val,
Value: val,
})
}
return opts
}
tests := []struct {
name string
param TemplateVersionParameter
value string
want bool
}{
// The function should never be called with 0 options, but if it is,
// it should always return false.
{
name: "empty",
want: false,
},
{
name: "no-options",
param: TemplateVersionParameter{
Options: make([]TemplateVersionParameterOption, 0),
},
},
{
name: "no-options-multi",
param: TemplateVersionParameter{
Type: provider.OptionTypeListString,
FormType: string(provider.ParameterFormTypeMultiSelect),
Options: make([]TemplateVersionParameterOption, 0),
},
want: false,
},
{
name: "no-options-list(string)",
param: TemplateVersionParameter{
Type: provider.OptionTypeListString,
FormType: "",
Options: make([]TemplateVersionParameterOption, 0),
},
want: false,
},
{
name: "list(string)-no-form",
param: TemplateVersionParameter{
Type: provider.OptionTypeListString,
FormType: "",
Options: options("red", "green", "blue"),
},
want: false,
value: `["red", "blue", "green"]`,
},
// now for some reasonable values
{
name: "list(string)-multi",
param: TemplateVersionParameter{
Type: provider.OptionTypeListString,
FormType: string(provider.ParameterFormTypeMultiSelect),
Options: options("red", "green", "blue"),
},
want: true,
value: `["red", "blue", "green"]`,
},
{
name: "string with json",
param: TemplateVersionParameter{
Type: provider.OptionTypeString,
Options: options(`["red","blue","green"]`, `["red","orange"]`),
},
want: true,
value: `["red","blue","green"]`,
},
{
name: "string",
param: TemplateVersionParameter{
Type: provider.OptionTypeString,
Options: options("red", "green", "blue"),
},
want: true,
value: "red",
},
// False values
{
name: "list(string)-multi",
param: TemplateVersionParameter{
Type: provider.OptionTypeListString,
FormType: string(provider.ParameterFormTypeMultiSelect),
Options: options("red", "green", "blue"),
},
want: false,
value: `["red", "blue", "purple"]`,
},
{
name: "string with json",
param: TemplateVersionParameter{
Type: provider.OptionTypeString,
Options: options(`["red","blue"]`, `["red","orange"]`),
},
want: false,
value: `["red","blue","green"]`,
},
{
name: "string",
param: TemplateVersionParameter{
Type: provider.OptionTypeString,
Options: options("red", "green", "blue"),
},
want: false,
value: "purple",
},
{
name: "list(string)-multi-scalar-value",
param: TemplateVersionParameter{
Type: provider.OptionTypeListString,
FormType: string(provider.ParameterFormTypeMultiSelect),
Options: options("red", "green", "blue"),
},
want: false,
value: "green",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := inOptionSet(tt.param, tt.value)
require.Equal(t, tt.want, got)
})
}
}
+19 -16
View File
@@ -54,22 +54,25 @@ const (
// TemplateVersionParameter represents a parameter for a template version.
type TemplateVersionParameter struct {
Name string `json:"name"`
DisplayName string `json:"display_name,omitempty"`
Description string `json:"description"`
DescriptionPlaintext string `json:"description_plaintext"`
Type string `json:"type" enums:"string,number,bool,list(string)"`
Mutable bool `json:"mutable"`
DefaultValue string `json:"default_value"`
Icon string `json:"icon"`
Options []TemplateVersionParameterOption `json:"options"`
ValidationError string `json:"validation_error,omitempty"`
ValidationRegex string `json:"validation_regex,omitempty"`
ValidationMin *int32 `json:"validation_min,omitempty"`
ValidationMax *int32 `json:"validation_max,omitempty"`
ValidationMonotonic ValidationMonotonicOrder `json:"validation_monotonic,omitempty" enums:"increasing,decreasing"`
Required bool `json:"required"`
Ephemeral bool `json:"ephemeral"`
Name string `json:"name"`
DisplayName string `json:"display_name,omitempty"`
Description string `json:"description"`
DescriptionPlaintext string `json:"description_plaintext"`
Type string `json:"type" enums:"string,number,bool,list(string)"`
// FormType has an enum value of empty string, `""`.
// Keep the leading comma in the enums struct tag.
FormType string `json:"form_type" enums:",radio,dropdown,input,textarea,slider,checkbox,switch,tag-select,multi-select,error"`
Mutable bool `json:"mutable"`
DefaultValue string `json:"default_value"`
Icon string `json:"icon"`
Options []TemplateVersionParameterOption `json:"options"`
ValidationError string `json:"validation_error,omitempty"`
ValidationRegex string `json:"validation_regex,omitempty"`
ValidationMin *int32 `json:"validation_min,omitempty"`
ValidationMax *int32 `json:"validation_max,omitempty"`
ValidationMonotonic ValidationMonotonicOrder `json:"validation_monotonic,omitempty" enums:"increasing,decreasing"`
Required bool `json:"required"`
Ephemeral bool `json:"ephemeral"`
}
// TemplateVersionParameterOption represents a selectable option for a template parameter.