mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
`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.
229 lines
7.4 KiB
Go
229 lines
7.4 KiB
Go
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"
|
|
)
|
|
|
|
func ValidateNewWorkspaceParameters(richParameters []TemplateVersionParameter, buildParameters []WorkspaceBuildParameter) error {
|
|
return ValidateWorkspaceBuildParameters(richParameters, buildParameters, nil)
|
|
}
|
|
|
|
func ValidateWorkspaceBuildParameters(richParameters []TemplateVersionParameter, buildParameters, lastBuildParameters []WorkspaceBuildParameter) error {
|
|
for _, richParameter := range richParameters {
|
|
buildParameter, foundBuildParameter := findBuildParameter(buildParameters, richParameter.Name)
|
|
lastBuildParameter, foundLastBuildParameter := findBuildParameter(lastBuildParameters, richParameter.Name)
|
|
|
|
if richParameter.Required && !foundBuildParameter && !foundLastBuildParameter {
|
|
return xerrors.Errorf("workspace build parameter %q is required", richParameter.Name)
|
|
}
|
|
|
|
if !foundBuildParameter && foundLastBuildParameter {
|
|
continue // previous build parameters have been validated before the last build
|
|
}
|
|
|
|
err := ValidateWorkspaceBuildParameter(richParameter, buildParameter, lastBuildParameter)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateWorkspaceBuildParameter(richParameter TemplateVersionParameter, buildParameter *WorkspaceBuildParameter, lastBuildParameter *WorkspaceBuildParameter) error {
|
|
err := validateBuildParameter(richParameter, buildParameter, lastBuildParameter)
|
|
if err != nil {
|
|
name := richParameter.Name
|
|
if richParameter.DisplayName != "" {
|
|
name = richParameter.DisplayName
|
|
}
|
|
return xerrors.Errorf("can't validate build parameter %q: %w", name, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateBuildParameter(richParameter TemplateVersionParameter, buildParameter *WorkspaceBuildParameter, lastBuildParameter *WorkspaceBuildParameter) error {
|
|
var (
|
|
current string
|
|
previous *string
|
|
)
|
|
|
|
if buildParameter != nil {
|
|
current = buildParameter.Value
|
|
}
|
|
|
|
if lastBuildParameter != nil {
|
|
previous = ptr.To(lastBuildParameter.Value)
|
|
}
|
|
|
|
if richParameter.Required && current == "" {
|
|
return xerrors.Errorf("parameter value is required")
|
|
}
|
|
|
|
if current == "" { // parameter is optional, so take the default value
|
|
current = richParameter.DefaultValue
|
|
}
|
|
|
|
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) {
|
|
return nil
|
|
}
|
|
|
|
var minVal, maxVal int
|
|
if richParameter.ValidationMin != nil {
|
|
minVal = int(*richParameter.ValidationMin)
|
|
}
|
|
if richParameter.ValidationMax != nil {
|
|
maxVal = int(*richParameter.ValidationMax)
|
|
}
|
|
|
|
validation := &provider.Validation{
|
|
Min: minVal,
|
|
Max: maxVal,
|
|
MinDisabled: richParameter.ValidationMin == nil,
|
|
MaxDisabled: richParameter.ValidationMax == nil,
|
|
Regex: richParameter.ValidationRegex,
|
|
Error: richParameter.ValidationError,
|
|
Monotonic: string(richParameter.ValidationMonotonic),
|
|
}
|
|
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
|
|
}
|
|
|
|
for _, p := range params {
|
|
if p.Name == parameterName {
|
|
return &p, true
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
func parameterValuesAsArray(options []TemplateVersionParameterOption) []string {
|
|
var arr []string
|
|
for _, opt := range options {
|
|
arr = append(arr, opt.Value)
|
|
}
|
|
return arr
|
|
}
|
|
|
|
func validationEnabled(param TemplateVersionParameter) bool {
|
|
return len(param.ValidationRegex) > 0 ||
|
|
param.ValidationMin != nil ||
|
|
param.ValidationMax != nil ||
|
|
len(param.ValidationMonotonic) > 0 ||
|
|
param.Type == "bool" || // boolean type doesn't have any custom validation rules, but the value must be checked (true/false).
|
|
param.Type == "list(string)" // list(string) type doesn't have special validation, but we need to check if this is a correct list.
|
|
}
|
|
|
|
// ParameterResolver should be populated with legacy workload and rich parameter values from the previous build. It then
|
|
// supports queries against a current TemplateVersionParameter to determine whether a new value is required, or a value
|
|
// correctly validates.
|
|
// @typescript-ignore ParameterResolver
|
|
type ParameterResolver struct {
|
|
Rich []WorkspaceBuildParameter
|
|
}
|
|
|
|
// ValidateResolve checks the provided value, v, against the parameter, p, and the previous build. If v is nil, it also
|
|
// resolves the correct value. It returns the value of the parameter, if valid, and an error if invalid.
|
|
func (r *ParameterResolver) ValidateResolve(p TemplateVersionParameter, v *WorkspaceBuildParameter) (value string, err error) {
|
|
prevV := r.findLastValue(p)
|
|
if !p.Mutable && v != nil && prevV != nil && v.Value != prevV.Value {
|
|
return "", xerrors.Errorf("Parameter %q is not mutable, so it can't be updated after creating a workspace.", p.Name)
|
|
}
|
|
if p.Required && v == nil && prevV == nil {
|
|
return "", xerrors.Errorf("Parameter %q is required but not provided", p.Name)
|
|
}
|
|
// First, the provided value
|
|
resolvedValue := v
|
|
// Second, previous value if not ephemeral
|
|
if resolvedValue == nil && !p.Ephemeral {
|
|
resolvedValue = prevV
|
|
}
|
|
// Last, default value
|
|
if resolvedValue == nil {
|
|
resolvedValue = &WorkspaceBuildParameter{
|
|
Name: p.Name,
|
|
Value: p.DefaultValue,
|
|
}
|
|
}
|
|
err = ValidateWorkspaceBuildParameter(p, resolvedValue, prevV)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return resolvedValue.Value, nil
|
|
}
|
|
|
|
// Resolve returns the value of the parameter. It does not do any validation,
|
|
// and is meant for use with the new dynamic parameters code path.
|
|
func (r *ParameterResolver) Resolve(p TemplateVersionParameter, v *WorkspaceBuildParameter) string {
|
|
prevV := r.findLastValue(p)
|
|
// First, the provided value
|
|
resolvedValue := v
|
|
// Second, previous value if not ephemeral
|
|
if resolvedValue == nil && !p.Ephemeral {
|
|
resolvedValue = prevV
|
|
}
|
|
// Last, default value
|
|
if resolvedValue == nil {
|
|
resolvedValue = &WorkspaceBuildParameter{
|
|
Name: p.Name,
|
|
Value: p.DefaultValue,
|
|
}
|
|
}
|
|
return resolvedValue.Value
|
|
}
|
|
|
|
// findLastValue finds the value from the previous build and returns it, or nil if the parameter had no value in the
|
|
// last build.
|
|
func (r *ParameterResolver) findLastValue(p TemplateVersionParameter) *WorkspaceBuildParameter {
|
|
for _, rp := range r.Rich {
|
|
if rp.Name == p.Name {
|
|
return &rp
|
|
}
|
|
}
|
|
return nil
|
|
}
|