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
+17
View File
@@ -16095,6 +16095,23 @@ const docTemplate = `{
"ephemeral": {
"type": "boolean"
},
"form_type": {
"description": "FormType has an enum value of empty string, ` + "`" + `\"\"` + "`" + `.\nKeep the leading comma in the enums struct tag.",
"type": "string",
"enum": [
"",
"radio",
"dropdown",
"input",
"textarea",
"slider",
"checkbox",
"switch",
"tag-select",
"multi-select",
"error"
]
},
"icon": {
"type": "string"
},
+17
View File
@@ -14662,6 +14662,23 @@
"ephemeral": {
"type": "boolean"
},
"form_type": {
"description": "FormType has an enum value of empty string, `\"\"`.\nKeep the leading comma in the enums struct tag.",
"type": "string",
"enum": [
"",
"radio",
"dropdown",
"input",
"textarea",
"slider",
"checkbox",
"switch",
"tag-select",
"multi-select",
"error"
]
},
"icon": {
"type": "string"
},
+7 -5
View File
@@ -92,13 +92,13 @@ func WorkspaceBuildParameters(params []database.WorkspaceBuildParameter) []coder
}
func TemplateVersionParameters(params []database.TemplateVersionParameter) ([]codersdk.TemplateVersionParameter, error) {
out := make([]codersdk.TemplateVersionParameter, len(params))
var err error
for i, p := range params {
out[i], err = TemplateVersionParameter(p)
out := make([]codersdk.TemplateVersionParameter, 0, len(params))
for _, p := range params {
np, err := TemplateVersionParameter(p)
if err != nil {
return nil, xerrors.Errorf("convert template version parameter %q: %w", p.Name, err)
}
out = append(out, np)
}
return out, nil
@@ -131,6 +131,7 @@ func TemplateVersionParameter(param database.TemplateVersionParameter) (codersdk
Description: param.Description,
DescriptionPlaintext: descriptionPlaintext,
Type: param.Type,
FormType: string(param.FormType),
Mutable: param.Mutable,
DefaultValue: param.DefaultValue,
Icon: param.Icon,
@@ -293,7 +294,8 @@ func templateVersionParameterOptions(rawOptions json.RawMessage) ([]codersdk.Tem
if err != nil {
return nil, err
}
var options []codersdk.TemplateVersionParameterOption
options := make([]codersdk.TemplateVersionParameterOption, 0)
for _, option := range protoOptions {
options = append(options, codersdk.TemplateVersionParameterOption{
Name: option.Name,
+1
View File
@@ -992,6 +992,7 @@ func TemplateVersionParameter(t testing.TB, db database.Store, orig database.Tem
Name: takeFirst(orig.Name, testutil.GetRandomName(t)),
Description: takeFirst(orig.Description, testutil.GetRandomName(t)),
Type: takeFirst(orig.Type, "string"),
FormType: orig.FormType, // empty string is ok!
Mutable: takeFirst(orig.Mutable, false),
DefaultValue: takeFirst(orig.DefaultValue, testutil.GetRandomName(t)),
Icon: takeFirst(orig.Icon, testutil.GetRandomName(t)),
+1
View File
@@ -9393,6 +9393,7 @@ func (q *FakeQuerier) InsertTemplateVersionParameter(_ context.Context, arg data
DisplayName: arg.DisplayName,
Description: arg.Description,
Type: arg.Type,
FormType: arg.FormType,
Mutable: arg.Mutable,
DefaultValue: arg.DefaultValue,
Icon: arg.Icon,
+19
View File
@@ -132,6 +132,22 @@ CREATE TYPE parameter_destination_scheme AS ENUM (
'provisioner_variable'
);
CREATE TYPE parameter_form_type AS ENUM (
'',
'error',
'radio',
'dropdown',
'input',
'textarea',
'slider',
'checkbox',
'switch',
'tag-select',
'multi-select'
);
COMMENT ON TYPE parameter_form_type IS 'Enum set should match the terraform provider set. This is defined as future form_types are not supported, and should be rejected. Always include the empty string for using the default form type.';
CREATE TYPE parameter_scope AS ENUM (
'template',
'import_job',
@@ -1434,6 +1450,7 @@ CREATE TABLE template_version_parameters (
display_name text DEFAULT ''::text NOT NULL,
display_order integer DEFAULT 0 NOT NULL,
ephemeral boolean DEFAULT false NOT NULL,
form_type parameter_form_type DEFAULT ''::parameter_form_type NOT NULL,
CONSTRAINT validation_monotonic_order CHECK ((validation_monotonic = ANY (ARRAY['increasing'::text, 'decreasing'::text, ''::text])))
);
@@ -1469,6 +1486,8 @@ COMMENT ON COLUMN template_version_parameters.display_order IS 'Specifies the or
COMMENT ON COLUMN template_version_parameters.ephemeral IS 'The value of an ephemeral parameter will not be preserved between consecutive workspace builds.';
COMMENT ON COLUMN template_version_parameters.form_type IS 'Specify what form_type should be used to render the parameter in the UI. Unsupported values are rejected.';
CREATE TABLE template_version_preset_parameters (
id uuid DEFAULT gen_random_uuid() NOT NULL,
template_version_preset_id uuid NOT NULL,
@@ -0,0 +1,2 @@
ALTER TABLE template_version_parameters DROP COLUMN form_type;
DROP TYPE parameter_form_type;
@@ -0,0 +1,11 @@
CREATE TYPE parameter_form_type AS ENUM ('', 'error', 'radio', 'dropdown', 'input', 'textarea', 'slider', 'checkbox', 'switch', 'tag-select', 'multi-select');
COMMENT ON TYPE parameter_form_type
IS 'Enum set should match the terraform provider set. This is defined as future form_types are not supported, and should be rejected. '
'Always include the empty string for using the default form type.';
-- Intentionally leaving the default blank. The provisioner will not re-run any
-- imports to backfill these values. Missing values just have to be handled.
ALTER TABLE template_version_parameters ADD COLUMN form_type parameter_form_type NOT NULL DEFAULT '';
COMMENT ON COLUMN template_version_parameters.form_type
IS 'Specify what form_type should be used to render the parameter in the UI. Unsupported values are rejected.';
+88
View File
@@ -1108,6 +1108,92 @@ func AllParameterDestinationSchemeValues() []ParameterDestinationScheme {
}
}
// Enum set should match the terraform provider set. This is defined as future form_types are not supported, and should be rejected. Always include the empty string for using the default form type.
type ParameterFormType string
const (
ParameterFormTypeValue0 ParameterFormType = ""
ParameterFormTypeError ParameterFormType = "error"
ParameterFormTypeRadio ParameterFormType = "radio"
ParameterFormTypeDropdown ParameterFormType = "dropdown"
ParameterFormTypeInput ParameterFormType = "input"
ParameterFormTypeTextarea ParameterFormType = "textarea"
ParameterFormTypeSlider ParameterFormType = "slider"
ParameterFormTypeCheckbox ParameterFormType = "checkbox"
ParameterFormTypeSwitch ParameterFormType = "switch"
ParameterFormTypeTagSelect ParameterFormType = "tag-select"
ParameterFormTypeMultiSelect ParameterFormType = "multi-select"
)
func (e *ParameterFormType) Scan(src interface{}) error {
switch s := src.(type) {
case []byte:
*e = ParameterFormType(s)
case string:
*e = ParameterFormType(s)
default:
return fmt.Errorf("unsupported scan type for ParameterFormType: %T", src)
}
return nil
}
type NullParameterFormType struct {
ParameterFormType ParameterFormType `json:"parameter_form_type"`
Valid bool `json:"valid"` // Valid is true if ParameterFormType is not NULL
}
// Scan implements the Scanner interface.
func (ns *NullParameterFormType) Scan(value interface{}) error {
if value == nil {
ns.ParameterFormType, ns.Valid = "", false
return nil
}
ns.Valid = true
return ns.ParameterFormType.Scan(value)
}
// Value implements the driver Valuer interface.
func (ns NullParameterFormType) Value() (driver.Value, error) {
if !ns.Valid {
return nil, nil
}
return string(ns.ParameterFormType), nil
}
func (e ParameterFormType) Valid() bool {
switch e {
case ParameterFormTypeValue0,
ParameterFormTypeError,
ParameterFormTypeRadio,
ParameterFormTypeDropdown,
ParameterFormTypeInput,
ParameterFormTypeTextarea,
ParameterFormTypeSlider,
ParameterFormTypeCheckbox,
ParameterFormTypeSwitch,
ParameterFormTypeTagSelect,
ParameterFormTypeMultiSelect:
return true
}
return false
}
func AllParameterFormTypeValues() []ParameterFormType {
return []ParameterFormType{
ParameterFormTypeValue0,
ParameterFormTypeError,
ParameterFormTypeRadio,
ParameterFormTypeDropdown,
ParameterFormTypeInput,
ParameterFormTypeTextarea,
ParameterFormTypeSlider,
ParameterFormTypeCheckbox,
ParameterFormTypeSwitch,
ParameterFormTypeTagSelect,
ParameterFormTypeMultiSelect,
}
}
type ParameterScope string
const (
@@ -3308,6 +3394,8 @@ type TemplateVersionParameter struct {
DisplayOrder int32 `db:"display_order" json:"display_order"`
// The value of an ephemeral parameter will not be preserved between consecutive workspace builds.
Ephemeral bool `db:"ephemeral" json:"ephemeral"`
// Specify what form_type should be used to render the parameter in the UI. Unsupported values are rejected.
FormType ParameterFormType `db:"form_type" json:"form_type"`
}
type TemplateVersionPreset struct {
+26 -20
View File
@@ -11178,7 +11178,7 @@ func (q *sqlQuerier) UpdateTemplateScheduleByID(ctx context.Context, arg UpdateT
}
const getTemplateVersionParameters = `-- name: GetTemplateVersionParameters :many
SELECT template_version_id, name, description, type, mutable, default_value, icon, options, validation_regex, validation_min, validation_max, validation_error, validation_monotonic, required, display_name, display_order, ephemeral FROM template_version_parameters WHERE template_version_id = $1 ORDER BY display_order ASC, LOWER(name) ASC
SELECT template_version_id, name, description, type, mutable, default_value, icon, options, validation_regex, validation_min, validation_max, validation_error, validation_monotonic, required, display_name, display_order, ephemeral, form_type FROM template_version_parameters WHERE template_version_id = $1 ORDER BY display_order ASC, LOWER(name) ASC
`
func (q *sqlQuerier) GetTemplateVersionParameters(ctx context.Context, templateVersionID uuid.UUID) ([]TemplateVersionParameter, error) {
@@ -11208,6 +11208,7 @@ func (q *sqlQuerier) GetTemplateVersionParameters(ctx context.Context, templateV
&i.DisplayName,
&i.DisplayOrder,
&i.Ephemeral,
&i.FormType,
); err != nil {
return nil, err
}
@@ -11229,6 +11230,7 @@ INSERT INTO
name,
description,
type,
form_type,
mutable,
default_value,
icon,
@@ -11261,28 +11263,30 @@ VALUES
$14,
$15,
$16,
$17
) RETURNING template_version_id, name, description, type, mutable, default_value, icon, options, validation_regex, validation_min, validation_max, validation_error, validation_monotonic, required, display_name, display_order, ephemeral
$17,
$18
) RETURNING template_version_id, name, description, type, mutable, default_value, icon, options, validation_regex, validation_min, validation_max, validation_error, validation_monotonic, required, display_name, display_order, ephemeral, form_type
`
type InsertTemplateVersionParameterParams struct {
TemplateVersionID uuid.UUID `db:"template_version_id" json:"template_version_id"`
Name string `db:"name" json:"name"`
Description string `db:"description" json:"description"`
Type string `db:"type" json:"type"`
Mutable bool `db:"mutable" json:"mutable"`
DefaultValue string `db:"default_value" json:"default_value"`
Icon string `db:"icon" json:"icon"`
Options json.RawMessage `db:"options" json:"options"`
ValidationRegex string `db:"validation_regex" json:"validation_regex"`
ValidationMin sql.NullInt32 `db:"validation_min" json:"validation_min"`
ValidationMax sql.NullInt32 `db:"validation_max" json:"validation_max"`
ValidationError string `db:"validation_error" json:"validation_error"`
ValidationMonotonic string `db:"validation_monotonic" json:"validation_monotonic"`
Required bool `db:"required" json:"required"`
DisplayName string `db:"display_name" json:"display_name"`
DisplayOrder int32 `db:"display_order" json:"display_order"`
Ephemeral bool `db:"ephemeral" json:"ephemeral"`
TemplateVersionID uuid.UUID `db:"template_version_id" json:"template_version_id"`
Name string `db:"name" json:"name"`
Description string `db:"description" json:"description"`
Type string `db:"type" json:"type"`
FormType ParameterFormType `db:"form_type" json:"form_type"`
Mutable bool `db:"mutable" json:"mutable"`
DefaultValue string `db:"default_value" json:"default_value"`
Icon string `db:"icon" json:"icon"`
Options json.RawMessage `db:"options" json:"options"`
ValidationRegex string `db:"validation_regex" json:"validation_regex"`
ValidationMin sql.NullInt32 `db:"validation_min" json:"validation_min"`
ValidationMax sql.NullInt32 `db:"validation_max" json:"validation_max"`
ValidationError string `db:"validation_error" json:"validation_error"`
ValidationMonotonic string `db:"validation_monotonic" json:"validation_monotonic"`
Required bool `db:"required" json:"required"`
DisplayName string `db:"display_name" json:"display_name"`
DisplayOrder int32 `db:"display_order" json:"display_order"`
Ephemeral bool `db:"ephemeral" json:"ephemeral"`
}
func (q *sqlQuerier) InsertTemplateVersionParameter(ctx context.Context, arg InsertTemplateVersionParameterParams) (TemplateVersionParameter, error) {
@@ -11291,6 +11295,7 @@ func (q *sqlQuerier) InsertTemplateVersionParameter(ctx context.Context, arg Ins
arg.Name,
arg.Description,
arg.Type,
arg.FormType,
arg.Mutable,
arg.DefaultValue,
arg.Icon,
@@ -11324,6 +11329,7 @@ func (q *sqlQuerier) InsertTemplateVersionParameter(ctx context.Context, arg Ins
&i.DisplayName,
&i.DisplayOrder,
&i.Ephemeral,
&i.FormType,
)
return i, err
}
@@ -5,6 +5,7 @@ INSERT INTO
name,
description,
type,
form_type,
mutable,
default_value,
icon,
@@ -37,7 +38,8 @@ VALUES
$14,
$15,
$16,
$17
$17,
$18
) RETURNING *;
-- name: GetTemplateVersionParameters :many
@@ -28,6 +28,7 @@ import (
protobuf "google.golang.org/protobuf/proto"
"cdr.dev/slog"
"github.com/coder/coder/v2/coderd/util/slice"
"github.com/coder/coder/v2/codersdk/drpcsdk"
@@ -1453,12 +1454,24 @@ func (s *server) completeTemplateImportJob(ctx context.Context, job database.Pro
}
}
pft, err := sdkproto.ProviderFormType(richParameter.FormType)
if err != nil {
return xerrors.Errorf("parameter %q: %w", richParameter.Name, err)
}
dft := database.ParameterFormType(pft)
if !dft.Valid() {
list := strings.Join(slice.ToStrings(database.AllParameterFormTypeValues()), ", ")
return xerrors.Errorf("parameter %q field 'form_type' not valid, currently supported: %s", richParameter.Name, list)
}
_, err = db.InsertTemplateVersionParameter(ctx, database.InsertTemplateVersionParameterParams{
TemplateVersionID: input.TemplateVersionID,
Name: richParameter.Name,
DisplayName: richParameter.DisplayName,
Description: richParameter.Description,
Type: richParameter.Type,
FormType: dft,
Mutable: richParameter.Mutable,
DefaultValue: richParameter.DefaultValue,
Icon: richParameter.Icon,
@@ -1384,6 +1384,60 @@ func TestCompleteJob(t *testing.T) {
})
})
t.Run("WorkspaceBuild_BadFormType", func(t *testing.T) {
t.Parallel()
srv, db, _, pd := setup(t, false, &overrides{})
jobID := uuid.New()
versionID := uuid.New()
err := db.InsertTemplateVersion(ctx, database.InsertTemplateVersionParams{
ID: versionID,
JobID: jobID,
OrganizationID: pd.OrganizationID,
})
require.NoError(t, err)
job, err := db.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{
ID: jobID,
Provisioner: database.ProvisionerTypeEcho,
Input: []byte(`{"template_version_id": "` + versionID.String() + `"}`),
StorageMethod: database.ProvisionerStorageMethodFile,
Type: database.ProvisionerJobTypeWorkspaceBuild,
OrganizationID: pd.OrganizationID,
})
require.NoError(t, err)
_, err = db.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{
OrganizationID: pd.OrganizationID,
WorkerID: uuid.NullUUID{
UUID: pd.ID,
Valid: true,
},
Types: []database.ProvisionerType{database.ProvisionerTypeEcho},
})
require.NoError(t, err)
_, err = srv.CompleteJob(ctx, &proto.CompletedJob{
JobId: job.ID.String(),
Type: &proto.CompletedJob_TemplateImport_{
TemplateImport: &proto.CompletedJob_TemplateImport{
StartResources: []*sdkproto.Resource{{
Name: "hello",
Type: "aws_instance",
}},
StopResources: []*sdkproto.Resource{},
RichParameters: []*sdkproto.RichParameter{
{
Name: "parameter",
Type: "string",
FormType: -1,
},
},
Plan: []byte("{}"),
},
},
})
require.Error(t, err)
require.ErrorContains(t, err, "unsupported form type")
})
t.Run("TemplateImport_MissingGitAuth", func(t *testing.T) {
t.Parallel()
srv, db, _, pd := setup(t, false, &overrides{})
+1 -64
View File
@@ -31,14 +31,12 @@ import (
"github.com/coder/coder/v2/coderd/provisionerdserver"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/coderd/rbac/policy"
"github.com/coder/coder/v2/coderd/render"
"github.com/coder/coder/v2/coderd/tracing"
"github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/examples"
"github.com/coder/coder/v2/provisioner/terraform/tfparse"
"github.com/coder/coder/v2/provisionersdk"
sdkproto "github.com/coder/coder/v2/provisionersdk/proto"
)
// @Summary Get template version by ID
@@ -307,7 +305,7 @@ func (api *API) templateVersionRichParameters(rw http.ResponseWriter, r *http.Re
return
}
templateVersionParameters, err := convertTemplateVersionParameters(dbTemplateVersionParameters)
templateVersionParameters, err := db2sdk.TemplateVersionParameters(dbTemplateVersionParameters)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error converting template version parameter.",
@@ -1869,67 +1867,6 @@ func convertTemplateVersion(version database.TemplateVersion, job codersdk.Provi
}
}
func convertTemplateVersionParameters(dbParams []database.TemplateVersionParameter) ([]codersdk.TemplateVersionParameter, error) {
params := make([]codersdk.TemplateVersionParameter, 0)
for _, dbParameter := range dbParams {
param, err := convertTemplateVersionParameter(dbParameter)
if err != nil {
return nil, err
}
params = append(params, param)
}
return params, nil
}
func convertTemplateVersionParameter(param database.TemplateVersionParameter) (codersdk.TemplateVersionParameter, error) {
var protoOptions []*sdkproto.RichParameterOption
err := json.Unmarshal(param.Options, &protoOptions)
if err != nil {
return codersdk.TemplateVersionParameter{}, err
}
options := make([]codersdk.TemplateVersionParameterOption, 0)
for _, option := range protoOptions {
options = append(options, codersdk.TemplateVersionParameterOption{
Name: option.Name,
Description: option.Description,
Value: option.Value,
Icon: option.Icon,
})
}
descriptionPlaintext, err := render.PlaintextFromMarkdown(param.Description)
if err != nil {
return codersdk.TemplateVersionParameter{}, err
}
var validationMin, validationMax *int32
if param.ValidationMin.Valid {
validationMin = &param.ValidationMin.Int32
}
if param.ValidationMax.Valid {
validationMax = &param.ValidationMax.Int32
}
return codersdk.TemplateVersionParameter{
Name: param.Name,
DisplayName: param.DisplayName,
Description: param.Description,
DescriptionPlaintext: descriptionPlaintext,
Type: param.Type,
Mutable: param.Mutable,
DefaultValue: param.DefaultValue,
Icon: param.Icon,
Options: options,
ValidationRegex: param.ValidationRegex,
ValidationMin: validationMin,
ValidationMax: validationMax,
ValidationError: param.ValidationError,
ValidationMonotonic: codersdk.ValidationMonotonicOrder(param.ValidationMonotonic),
Required: param.Required,
Ephemeral: param.Ephemeral,
}, nil
}
func convertTemplateVersionVariables(dbVariables []database.TemplateVersionVariable) []codersdk.TemplateVersionVariable {
variables := make([]codersdk.TemplateVersionVariable, 0)
for _, dbVariable := range dbVariables {
+98 -1
View File
@@ -42,6 +42,7 @@ import (
"github.com/coder/coder/v2/provisioner/echo"
"github.com/coder/coder/v2/provisionersdk/proto"
"github.com/coder/coder/v2/testutil"
"github.com/coder/terraform-provider-coder/v2/provider"
)
func TestWorkspace(t *testing.T) {
@@ -3527,6 +3528,12 @@ func TestWorkspaceWithRichParameters(t *testing.T) {
secondParameterDescription = "_This_ is second *parameter*"
secondParameterValue = "2"
secondParameterValidationMonotonic = codersdk.MonotonicOrderIncreasing
thirdParameterName = "third_parameter"
thirdParameterType = "list(string)"
thirdParameterFormType = proto.ParameterFormType_MULTISELECT
thirdParameterDefault = `["red"]`
thirdParameterOption = "red"
)
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
@@ -3542,6 +3549,7 @@ func TestWorkspaceWithRichParameters(t *testing.T) {
Name: firstParameterName,
Type: firstParameterType,
Description: firstParameterDescription,
FormType: proto.ParameterFormType_INPUT,
},
{
Name: secondParameterName,
@@ -3551,6 +3559,19 @@ func TestWorkspaceWithRichParameters(t *testing.T) {
ValidationMin: ptr.Ref(int32(1)),
ValidationMax: ptr.Ref(int32(3)),
ValidationMonotonic: string(secondParameterValidationMonotonic),
FormType: proto.ParameterFormType_INPUT,
},
{
Name: thirdParameterName,
Type: thirdParameterType,
DefaultValue: thirdParameterDefault,
Options: []*proto.RichParameterOption{
{
Name: thirdParameterOption,
Value: thirdParameterOption,
},
},
FormType: thirdParameterFormType,
},
},
},
@@ -3575,12 +3596,13 @@ func TestWorkspaceWithRichParameters(t *testing.T) {
templateRichParameters, err := client.TemplateVersionRichParameters(ctx, version.ID)
require.NoError(t, err)
require.Len(t, templateRichParameters, 2)
require.Len(t, templateRichParameters, 3)
require.Equal(t, firstParameterName, templateRichParameters[0].Name)
require.Equal(t, firstParameterType, templateRichParameters[0].Type)
require.Equal(t, firstParameterDescription, templateRichParameters[0].Description)
require.Equal(t, firstParameterDescriptionPlaintext, templateRichParameters[0].DescriptionPlaintext)
require.Equal(t, codersdk.ValidationMonotonicOrder(""), templateRichParameters[0].ValidationMonotonic) // no validation for string
require.Equal(t, secondParameterName, templateRichParameters[1].Name)
require.Equal(t, secondParameterDisplayName, templateRichParameters[1].DisplayName)
require.Equal(t, secondParameterType, templateRichParameters[1].Type)
@@ -3588,9 +3610,18 @@ func TestWorkspaceWithRichParameters(t *testing.T) {
require.Equal(t, secondParameterDescriptionPlaintext, templateRichParameters[1].DescriptionPlaintext)
require.Equal(t, secondParameterValidationMonotonic, templateRichParameters[1].ValidationMonotonic)
third := templateRichParameters[2]
require.Equal(t, thirdParameterName, third.Name)
require.Equal(t, thirdParameterType, third.Type)
require.Equal(t, string(database.ParameterFormTypeMultiSelect), third.FormType)
require.Equal(t, thirdParameterDefault, third.DefaultValue)
require.Equal(t, thirdParameterOption, third.Options[0].Name)
require.Equal(t, thirdParameterOption, third.Options[0].Value)
expectedBuildParameters := []codersdk.WorkspaceBuildParameter{
{Name: firstParameterName, Value: firstParameterValue},
{Name: secondParameterName, Value: secondParameterValue},
{Name: thirdParameterName, Value: thirdParameterDefault},
}
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
@@ -3606,6 +3637,72 @@ func TestWorkspaceWithRichParameters(t *testing.T) {
require.ElementsMatch(t, expectedBuildParameters, workspaceBuildParameters)
}
func TestWorkspaceWithMultiSelectFailure(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: []*proto.Response{
{
Type: &proto.Response_Plan{
Plan: &proto.PlanComplete{
Parameters: []*proto.RichParameter{
{
Name: "param",
Type: provider.OptionTypeListString,
DefaultValue: `["red"]`,
Options: []*proto.RichParameterOption{
{
Name: "red",
Value: "red",
},
},
FormType: proto.ParameterFormType_MULTISELECT,
},
},
},
},
},
},
ProvisionApply: []*proto.Response{{
Type: &proto.Response_Apply{
Apply: &proto.ApplyComplete{},
},
}},
})
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
templateRichParameters, err := client.TemplateVersionRichParameters(ctx, version.ID)
require.NoError(t, err)
require.Len(t, templateRichParameters, 1)
expectedBuildParameters := []codersdk.WorkspaceBuildParameter{
// purple is not in the response set
{Name: "param", Value: `["red", "purple"]`},
}
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
req := codersdk.CreateWorkspaceRequest{
TemplateID: template.ID,
Name: coderdtest.RandomUsername(t),
AutostartSchedule: ptr.Ref("CRON_TZ=US/Central 30 9 * * 1-5"),
TTLMillis: ptr.Ref((8 * time.Hour).Milliseconds()),
AutomaticUpdates: codersdk.AutomaticUpdatesNever,
RichParameterValues: expectedBuildParameters,
}
_, err = client.CreateUserWorkspace(context.Background(), codersdk.Me, req)
require.Error(t, err)
var apiError *codersdk.Error
require.ErrorAs(t, err, &apiError)
require.Equal(t, http.StatusBadRequest, apiError.StatusCode())
}
func TestWorkspaceWithOptionalRichParameters(t *testing.T) {
t.Parallel()
+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.
+31 -18
View File
@@ -7185,6 +7185,7 @@ Restarts will only happen on weekdays in this list on weeks which line up with W
"description_plaintext": "string",
"display_name": "string",
"ephemeral": true,
"form_type": "",
"icon": "string",
"mutable": true,
"name": "string",
@@ -7208,29 +7209,41 @@ Restarts will only happen on weekdays in this list on weeks which line up with W
### Properties
| Name | Type | Required | Restrictions | Description |
|-------------------------|---------------------------------------------------------------------------------------------|----------|--------------|-------------|
| `default_value` | string | false | | |
| `description` | string | false | | |
| `description_plaintext` | string | false | | |
| `display_name` | string | false | | |
| `ephemeral` | boolean | false | | |
| `icon` | string | false | | |
| `mutable` | boolean | false | | |
| `name` | string | false | | |
| `options` | array of [codersdk.TemplateVersionParameterOption](#codersdktemplateversionparameteroption) | false | | |
| `required` | boolean | false | | |
| `type` | string | false | | |
| `validation_error` | string | false | | |
| `validation_max` | integer | false | | |
| `validation_min` | integer | false | | |
| `validation_monotonic` | [codersdk.ValidationMonotonicOrder](#codersdkvalidationmonotonicorder) | false | | |
| `validation_regex` | string | false | | |
| Name | Type | Required | Restrictions | Description |
|-------------------------|---------------------------------------------------------------------------------------------|----------|--------------|----------------------------------------------------------------------------------------------------|
| `default_value` | string | false | | |
| `description` | string | false | | |
| `description_plaintext` | string | false | | |
| `display_name` | string | false | | |
| `ephemeral` | boolean | false | | |
| `form_type` | string | false | | Form type has an enum value of empty string, `""`. Keep the leading comma in the enums struct tag. |
| `icon` | string | false | | |
| `mutable` | boolean | false | | |
| `name` | string | false | | |
| `options` | array of [codersdk.TemplateVersionParameterOption](#codersdktemplateversionparameteroption) | false | | |
| `required` | boolean | false | | |
| `type` | string | false | | |
| `validation_error` | string | false | | |
| `validation_max` | integer | false | | |
| `validation_min` | integer | false | | |
| `validation_monotonic` | [codersdk.ValidationMonotonicOrder](#codersdkvalidationmonotonicorder) | false | | |
| `validation_regex` | string | false | | |
#### Enumerated Values
| Property | Value |
|------------------------|----------------|
| `form_type` | `` |
| `form_type` | `radio` |
| `form_type` | `dropdown` |
| `form_type` | `input` |
| `form_type` | `textarea` |
| `form_type` | `slider` |
| `form_type` | `checkbox` |
| `form_type` | `switch` |
| `form_type` | `tag-select` |
| `form_type` | `multi-select` |
| `form_type` | `error` |
| `type` | `string` |
| `type` | `number` |
| `type` | `bool` |
+36 -23
View File
@@ -3165,6 +3165,7 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/r
"description_plaintext": "string",
"display_name": "string",
"ephemeral": true,
"form_type": "",
"icon": "string",
"mutable": true,
"name": "string",
@@ -3197,34 +3198,46 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/r
Status Code **200**
| Name | Type | Required | Restrictions | Description |
|---------------------------|----------------------------------------------------------------------------------|----------|--------------|-------------|
| `[array item]` | array | false | | |
| `» default_value` | string | false | | |
| `» description` | string | false | | |
| `» description_plaintext` | string | false | | |
| `» display_name` | string | false | | |
| `» ephemeral` | boolean | false | | |
| icon` | string | false | | |
| mutable` | boolean | false | | |
| name` | string | false | | |
| options` | array | false | | |
| » description` | string | false | | |
| `»» icon` | string | false | | |
| `»» name` | string | false | | |
| `»» value` | string | false | | |
| required` | boolean | false | | |
| type` | string | false | | |
| validation_error` | string | false | | |
| `» validation_max` | integer | false | | |
| `» validation_min` | integer | false | | |
| `» validation_monotonic` | [codersdk.ValidationMonotonicOrder](schemas.md#codersdkvalidationmonotonicorder) | false | | |
| `» validation_regex` | string | false | | |
| Name | Type | Required | Restrictions | Description |
|---------------------------|----------------------------------------------------------------------------------|----------|--------------|----------------------------------------------------------------------------------------------------|
| `[array item]` | array | false | | |
| `» default_value` | string | false | | |
| `» description` | string | false | | |
| `» description_plaintext` | string | false | | |
| `» display_name` | string | false | | |
| `» ephemeral` | boolean | false | | |
| form_type` | string | false | | Form type has an enum value of empty string, `""`. Keep the leading comma in the enums struct tag. |
| icon` | string | false | | |
| mutable` | boolean | false | | |
| name` | string | false | | |
| options` | array | false | | |
| `»» description` | string | false | | |
| `»» icon` | string | false | | |
| `»» name` | string | false | | |
| » value` | string | false | | |
| required` | boolean | false | | |
| type` | string | false | | |
| `» validation_error` | string | false | | |
| `» validation_max` | integer | false | | |
| `» validation_min` | integer | false | | |
| `» validation_monotonic` | [codersdk.ValidationMonotonicOrder](schemas.md#codersdkvalidationmonotonicorder) | false | | |
| `» validation_regex` | string | false | | |
#### Enumerated Values
| Property | Value |
|------------------------|----------------|
| `form_type` | `` |
| `form_type` | `radio` |
| `form_type` | `dropdown` |
| `form_type` | `input` |
| `form_type` | `textarea` |
| `form_type` | `slider` |
| `form_type` | `checkbox` |
| `form_type` | `switch` |
| `form_type` | `tag-select` |
| `form_type` | `multi-select` |
| `form_type` | `error` |
| `type` | `string` |
| `type` | `number` |
| `type` | `bool` |
+7
View File
@@ -757,10 +757,17 @@ func ConvertState(ctx context.Context, modules []*tfjson.StateModule, rawGraph s
if param.Default != nil {
defaultVal = *param.Default
}
pft, err := proto.FormType(param.FormType)
if err != nil {
return nil, xerrors.Errorf("decode form_type for coder_parameter.%s: %w", resource.Name, err)
}
protoParam := &proto.RichParameter{
Name: param.Name,
DisplayName: param.DisplayName,
Description: param.Description,
FormType: pft,
Type: param.Type,
Mutable: param.Mutable,
DefaultValue: defaultVal,
+23
View File
@@ -608,24 +608,28 @@ func TestConvertResources(t *testing.T) {
Description: "First parameter from child module",
Mutable: true,
DefaultValue: "abcdef",
FormType: proto.ParameterFormType_INPUT,
}, {
Name: "Second parameter from child module",
Type: "string",
Description: "Second parameter from child module",
Mutable: true,
DefaultValue: "ghijkl",
FormType: proto.ParameterFormType_INPUT,
}, {
Name: "First parameter from module",
Type: "string",
Description: "First parameter from module",
Mutable: true,
DefaultValue: "abcdef",
FormType: proto.ParameterFormType_INPUT,
}, {
Name: "Second parameter from module",
Type: "string",
Description: "Second parameter from module",
Mutable: true,
DefaultValue: "ghijkl",
FormType: proto.ParameterFormType_INPUT,
}, {
Name: "Example",
Type: "string",
@@ -637,35 +641,41 @@ func TestConvertResources(t *testing.T) {
Value: "second",
}},
Required: true,
FormType: proto.ParameterFormType_RADIO,
}, {
Name: "number_example",
Type: "number",
DefaultValue: "4",
ValidationMin: nil,
ValidationMax: nil,
FormType: proto.ParameterFormType_INPUT,
}, {
Name: "number_example_max_zero",
Type: "number",
DefaultValue: "-2",
ValidationMin: terraform.PtrInt32(-3),
ValidationMax: terraform.PtrInt32(0),
FormType: proto.ParameterFormType_INPUT,
}, {
Name: "number_example_min_max",
Type: "number",
DefaultValue: "4",
ValidationMin: terraform.PtrInt32(3),
ValidationMax: terraform.PtrInt32(6),
FormType: proto.ParameterFormType_INPUT,
}, {
Name: "number_example_min_zero",
Type: "number",
DefaultValue: "4",
ValidationMin: terraform.PtrInt32(0),
ValidationMax: terraform.PtrInt32(6),
FormType: proto.ParameterFormType_INPUT,
}, {
Name: "Sample",
Type: "string",
Description: "blah blah",
DefaultValue: "ok",
FormType: proto.ParameterFormType_INPUT,
}},
},
"rich-parameters-order": {
@@ -688,12 +698,14 @@ func TestConvertResources(t *testing.T) {
Type: "string",
Required: true,
Order: 55,
FormType: proto.ParameterFormType_INPUT,
}, {
Name: "Sample",
Type: "string",
Description: "blah blah",
DefaultValue: "ok",
Order: 99,
FormType: proto.ParameterFormType_INPUT,
}},
},
"rich-parameters-validation": {
@@ -719,36 +731,42 @@ func TestConvertResources(t *testing.T) {
Mutable: true,
ValidationMin: nil,
ValidationMax: nil,
FormType: proto.ParameterFormType_INPUT,
}, {
Name: "number_example_max",
Type: "number",
DefaultValue: "4",
ValidationMin: nil,
ValidationMax: terraform.PtrInt32(6),
FormType: proto.ParameterFormType_INPUT,
}, {
Name: "number_example_max_zero",
Type: "number",
DefaultValue: "-3",
ValidationMin: nil,
ValidationMax: terraform.PtrInt32(0),
FormType: proto.ParameterFormType_INPUT,
}, {
Name: "number_example_min",
Type: "number",
DefaultValue: "4",
ValidationMin: terraform.PtrInt32(3),
ValidationMax: nil,
FormType: proto.ParameterFormType_INPUT,
}, {
Name: "number_example_min_max",
Type: "number",
DefaultValue: "4",
ValidationMin: terraform.PtrInt32(3),
ValidationMax: terraform.PtrInt32(6),
FormType: proto.ParameterFormType_INPUT,
}, {
Name: "number_example_min_zero",
Type: "number",
DefaultValue: "4",
ValidationMin: terraform.PtrInt32(0),
ValidationMax: nil,
FormType: proto.ParameterFormType_INPUT,
}},
},
"external-auth-providers": {
@@ -824,29 +842,34 @@ func TestConvertResources(t *testing.T) {
Description: "First parameter from child module",
Mutable: true,
DefaultValue: "abcdef",
FormType: proto.ParameterFormType_INPUT,
}, {
Name: "Second parameter from child module",
Type: "string",
Description: "Second parameter from child module",
Mutable: true,
DefaultValue: "ghijkl",
FormType: proto.ParameterFormType_INPUT,
}, {
Name: "First parameter from module",
Type: "string",
Description: "First parameter from module",
Mutable: true,
DefaultValue: "abcdef",
FormType: proto.ParameterFormType_INPUT,
}, {
Name: "Second parameter from module",
Type: "string",
Description: "Second parameter from module",
Mutable: true,
DefaultValue: "ghijkl",
FormType: proto.ParameterFormType_INPUT,
}, {
Name: "Sample",
Type: "string",
Description: "blah blah",
DefaultValue: "ok",
FormType: proto.ParameterFormType_INPUT,
}},
Presets: []*proto.Preset{{
Name: "My First Project",
+1
View File
@@ -28,6 +28,7 @@ import "github.com/coder/coder/v2/apiversion"
// - Add new field named `expiration_policy` to `Prebuild`, with a field named
// `ttl` to define TTL-based expiration for unclaimed prebuilds.
// - Add `group` field to `App`
// - Add `form_type` field to parameters
const (
CurrentMajor = 1
CurrentMinor = 6
+63
View File
@@ -0,0 +1,63 @@
package proto
import (
"golang.org/x/xerrors"
"github.com/coder/terraform-provider-coder/v2/provider"
)
func ProviderFormType(ft ParameterFormType) (provider.ParameterFormType, error) {
switch ft {
case ParameterFormType_DEFAULT:
return provider.ParameterFormTypeDefault, nil
case ParameterFormType_FORM_ERROR:
return provider.ParameterFormTypeError, nil
case ParameterFormType_RADIO:
return provider.ParameterFormTypeRadio, nil
case ParameterFormType_DROPDOWN:
return provider.ParameterFormTypeDropdown, nil
case ParameterFormType_INPUT:
return provider.ParameterFormTypeInput, nil
case ParameterFormType_TEXTAREA:
return provider.ParameterFormTypeTextArea, nil
case ParameterFormType_SLIDER:
return provider.ParameterFormTypeSlider, nil
case ParameterFormType_CHECKBOX:
return provider.ParameterFormTypeCheckbox, nil
case ParameterFormType_SWITCH:
return provider.ParameterFormTypeSwitch, nil
case ParameterFormType_TAGSELECT:
return provider.ParameterFormTypeTagSelect, nil
case ParameterFormType_MULTISELECT:
return provider.ParameterFormTypeMultiSelect, nil
}
return provider.ParameterFormTypeDefault, xerrors.Errorf("unsupported form type: %s", ft)
}
func FormType(ft provider.ParameterFormType) (ParameterFormType, error) {
switch ft {
case provider.ParameterFormTypeDefault:
return ParameterFormType_DEFAULT, nil
case provider.ParameterFormTypeError:
return ParameterFormType_FORM_ERROR, nil
case provider.ParameterFormTypeRadio:
return ParameterFormType_RADIO, nil
case provider.ParameterFormTypeDropdown:
return ParameterFormType_DROPDOWN, nil
case provider.ParameterFormTypeInput:
return ParameterFormType_INPUT, nil
case provider.ParameterFormTypeTextArea:
return ParameterFormType_TEXTAREA, nil
case provider.ParameterFormTypeSlider:
return ParameterFormType_SLIDER, nil
case provider.ParameterFormTypeCheckbox:
return ParameterFormType_CHECKBOX, nil
case provider.ParameterFormTypeSwitch:
return ParameterFormType_SWITCH, nil
case provider.ParameterFormTypeTagSelect:
return ParameterFormType_TAGSELECT, nil
case provider.ParameterFormTypeMultiSelect:
return ParameterFormType_MULTISELECT, nil
}
return ParameterFormType_DEFAULT, xerrors.Errorf("unsupported form type: %s", ft)
}
+26
View File
@@ -0,0 +1,26 @@
package proto_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/provisionersdk/proto"
"github.com/coder/terraform-provider-coder/v2/provider"
)
// TestProviderFormTypeEnum keeps the provider.ParameterFormTypes() enum in sync with the
// proto.FormType enum. If a new form type is added to the provider, it should also be added
// to the proto file.
func TestProviderFormTypeEnum(t *testing.T) {
t.Parallel()
all := provider.ParameterFormTypes()
for _, p := range all {
t.Run(string(p), func(t *testing.T) {
t.Parallel()
_, err := proto.FormType(p)
require.NoError(t, err, "proto form type should be valid, add it to the proto file")
})
}
}
+758 -661
View File
File diff suppressed because it is too large Load Diff
+15
View File
@@ -27,6 +27,20 @@ message RichParameterOption {
string icon = 4;
}
enum ParameterFormType {
DEFAULT = 0;
FORM_ERROR = 1;
RADIO = 2;
DROPDOWN = 3;
INPUT = 4;
TEXTAREA = 5;
SLIDER = 6;
CHECKBOX = 7;
SWITCH = 8;
TAGSELECT = 9;
MULTISELECT = 10;
}
// RichParameter represents a variable that is exposed.
message RichParameter {
reserved 14;
@@ -49,6 +63,7 @@ message RichParameter {
string display_name = 15;
int32 order = 16;
bool ephemeral = 17;
ParameterFormType form_type = 18;
}
// RichParameterValue holds the key/value mapping of a parameter.
+4 -1
View File
@@ -1,4 +1,4 @@
import type { RichParameter } from "./provisionerGenerated";
import { ParameterFormType, type RichParameter } from "./provisionerGenerated";
// Rich parameters
@@ -19,6 +19,7 @@ export const emptyParameter: RichParameter = {
displayName: "",
order: 0,
ephemeral: false,
formType: ParameterFormType.DEFAULT,
};
// firstParameter is mutable string with a default value (parameter value not required).
@@ -149,6 +150,7 @@ export const firstBuildOption: RichParameter = {
defaultValue: "ABCDEF",
mutable: true,
ephemeral: true,
options: [],
};
export const secondBuildOption: RichParameter = {
@@ -161,4 +163,5 @@ export const secondBuildOption: RichParameter = {
defaultValue: "false",
mutable: true,
ephemeral: true,
options: [],
};
+19
View File
@@ -5,6 +5,21 @@ import { Timestamp } from "./google/protobuf/timestampGenerated";
export const protobufPackage = "provisioner";
export enum ParameterFormType {
DEFAULT = 0,
FORM_ERROR = 1,
RADIO = 2,
DROPDOWN = 3,
INPUT = 4,
TEXTAREA = 5,
SLIDER = 6,
CHECKBOX = 7,
SWITCH = 8,
TAGSELECT = 9,
MULTISELECT = 10,
UNRECOGNIZED = -1,
}
/** LogLevel represents severity of the log. */
export enum LogLevel {
TRACE = 0,
@@ -96,6 +111,7 @@ export interface RichParameter {
displayName: string;
order: number;
ephemeral: boolean;
formType: ParameterFormType;
}
/** RichParameterValue holds the key/value mapping of a parameter. */
@@ -539,6 +555,9 @@ export const RichParameter = {
if (message.ephemeral === true) {
writer.uint32(136).bool(message.ephemeral);
}
if (message.formType !== 0) {
writer.uint32(144).int32(message.formType);
}
return writer;
},
};
+1
View File
@@ -2819,6 +2819,7 @@ export interface TemplateVersionParameter {
readonly description: string;
readonly description_plaintext: string;
readonly type: string;
readonly form_type: string;
readonly mutable: boolean;
readonly default_value: string;
readonly icon: string;
@@ -19,6 +19,7 @@ const createTemplateVersionParameter = (
name: "first_parameter",
description: "This is first parameter.",
type: "string",
form_type: "input",
mutable: true,
default_value: "default string",
icon: "/icon/folder.svg",
@@ -94,6 +94,7 @@ export const TemplateVersionParameters: Story = {
description: "Select the deployment region.",
description_plaintext: "Select the deployment region.",
type: "string",
form_type: "radio",
mutable: false,
default_value: "us-west-1",
icon: "",
@@ -110,6 +111,7 @@ export const TemplateVersionParameters: Story = {
description: "Number of CPU cores.",
description_plaintext: "Number of CPU cores.",
type: "number",
form_type: "input",
mutable: true,
default_value: "4",
icon: "",
@@ -77,6 +77,7 @@ export const Parameters: Story = {
description: "",
description_plaintext: "",
type: "string",
form_type: "radio",
mutable: false,
default_value: "",
icon: "/emojis/1f30e.png",
+5
View File
@@ -1604,6 +1604,7 @@ export const MockTemplateVersionParameter1: TypesGen.TemplateVersionParameter =
{
name: "first_parameter",
type: "string",
form_type: "input",
description: "This is first parameter",
description_plaintext: "Markdown: This is first parameter",
default_value: "abc",
@@ -1618,6 +1619,7 @@ export const MockTemplateVersionParameter2: TypesGen.TemplateVersionParameter =
{
name: "second_parameter",
type: "number",
form_type: "input",
description: "This is second parameter",
description_plaintext: "Markdown: This is second parameter",
default_value: "2",
@@ -1635,6 +1637,7 @@ export const MockTemplateVersionParameter3: TypesGen.TemplateVersionParameter =
{
name: "third_parameter",
type: "string",
form_type: "input",
description: "This is third parameter",
description_plaintext: "Markdown: This is third parameter",
default_value: "aaa",
@@ -1651,6 +1654,7 @@ export const MockTemplateVersionParameter4: TypesGen.TemplateVersionParameter =
{
name: "fourth_parameter",
type: "string",
form_type: "input",
description: "This is fourth parameter",
description_plaintext: "Markdown: This is fourth parameter",
default_value: "def",
@@ -1664,6 +1668,7 @@ export const MockTemplateVersionParameter4: TypesGen.TemplateVersionParameter =
const MockTemplateVersionParameter5: TypesGen.TemplateVersionParameter = {
name: "fifth_parameter",
type: "number",
form_type: "input",
description: "This is fifth parameter",
description_plaintext: "Markdown: This is fifth parameter",
default_value: "5",
+1
View File
@@ -9,6 +9,7 @@ test("getInitialRichParameterValues return default value when default build para
description: "The number of CPU cores",
description_plaintext: "The number of CPU cores",
type: "string",
form_type: "radio",
mutable: true,
default_value: "2",
icon: "/icon/memory.svg",