@@ -21,6 +21,15 @@ import (
2121 "golang.org/x/xerrors"
2222)
2323
24+ type ValidationMode string
25+
26+ const (
27+ // ValidationModeDefault is used for creating a workspace. It validates the
28+ // final value used for a parameter.
29+ ValidationModeDefault ValidationMode = ""
30+ ValidationModeTemplateImport ValidationMode = "template-import"
31+ )
32+
2433var (
2534 defaultValuePath = cty.Path {cty.GetAttrStep {Name : "default" }}
2635)
@@ -56,7 +65,7 @@ type Parameter struct {
5665 Type OptionType
5766 FormType ParameterFormType
5867 Mutable bool
59- Default string
68+ Default * string
6069 Icon string
6170 Option []Option
6271 Validation []Validation
@@ -105,10 +114,16 @@ func parameterDataSource() *schema.Resource {
105114 Type : rd .Get ("type" ),
106115 FormType : rd .Get ("form_type" ),
107116 Mutable : rd .Get ("mutable" ),
108- Default : rd .Get ("default" ),
109- Icon : rd .Get ("icon" ),
110- Option : rd .Get ("option" ),
111- Validation : fixedValidation ,
117+ Default : func () * string {
118+ if rd .GetRawConfig ().AsValueMap ()["default" ].IsNull () {
119+ return nil
120+ }
121+ val , _ := rd .Get ("default" ).(string )
122+ return & val
123+ }(),
124+ Icon : rd .Get ("icon" ),
125+ Option : rd .Get ("option" ),
126+ Validation : fixedValidation ,
112127 Optional : func () bool {
113128 // This hack allows for checking if the "default" field is present in the .tf file.
114129 // If "default" is missing or is "null", then it means that this field is required,
@@ -124,25 +139,6 @@ func parameterDataSource() *schema.Resource {
124139 return diag .Errorf ("decode parameter: %s" , err )
125140 }
126141
127- var value * string
128- if ! rd .GetRawConfig ().AsValueMap ()["default" ].IsNull () {
129- value = & parameter .Default
130- }
131-
132- envValue , ok := os .LookupEnv (ParameterEnvironmentVariable (parameter .Name ))
133- if ok {
134- value = & envValue
135- }
136-
137- if value != nil {
138- rd .Set ("value" , value )
139- } else {
140- // Maintaining backwards compatibility. The previous behavior was
141- // to write back an empty string.
142- // TODO: Should an empty string exist if no value is set?
143- rd .Set ("value" , "" )
144- }
145-
146142 if ! parameter .Mutable && parameter .Ephemeral {
147143 return diag .Errorf ("parameter can't be immutable and ephemeral" )
148144 }
@@ -151,10 +147,17 @@ func parameterDataSource() *schema.Resource {
151147 return diag .Errorf ("ephemeral parameter requires the default property" )
152148 }
153149
154- diags := parameter .Valid (value )
150+ var input * string
151+ envValue , ok := os .LookupEnv (ParameterEnvironmentVariable (parameter .Name ))
152+ if ok {
153+ input = & envValue
154+ }
155+
156+ value , diags := parameter .Valid (input , ValidationModeDefault )
155157 if diags .HasError () {
156158 return diags
157159 }
160+ rd .Set ("value" , value )
158161
159162 // Set the form_type as it could have changed in the validation.
160163 rd .Set ("form_type" , parameter .FormType )
@@ -397,15 +400,20 @@ func valueIsType(typ OptionType, value string) error {
397400 return nil
398401}
399402
400- func (v * Parameter ) Valid (value * string ) diag.Diagnostics {
403+ func (v * Parameter ) Valid (input * string , mode ValidationMode ) ( string , diag.Diagnostics ) {
401404 var err error
402405 var optionType OptionType
403406
407+ value := input
408+ if input == nil {
409+ value = v .Default
410+ }
411+
404412 // optionType might differ from parameter.Type. This is ok, and parameter.Type
405413 // should be used for the value type, and optionType for options.
406414 optionType , v .FormType , err = ValidateFormType (v .Type , len (v .Option ), v .FormType )
407415 if err != nil {
408- return diag.Diagnostics {
416+ return "" , diag.Diagnostics {
409417 {
410418 Severity : diag .Error ,
411419 Summary : "Invalid form_type for parameter" ,
@@ -417,28 +425,28 @@ func (v *Parameter) Valid(value *string) diag.Diagnostics {
417425
418426 optionValues , diags := v .ValidOptions (optionType )
419427 if diags .HasError () {
420- return diags
428+ return "" , diags
421429 }
422430
423- // TODO: The default value should also be validated
424- //if v.Default != "" {
425- // err := valueIsType(v.Type, v.Default)
426- // if err != nil {
427- // return diag.Diagnostics{
428- // {
429- // Severity: diag.Error,
430- // Summary: fmt.Sprintf("Default value is not of type %q", v.Type),
431- // Detail: err.Error(),
432- // AttributePath: defaultValuePath,
433- // },
434- // }
435- // }
436- //
437- // d := v.validValue(v.Default, optionType, optionValues, defaultValuePath)
438- // if d.HasError() {
439- // return d
440- // }
441- // }
431+ if mode == ValidationModeTemplateImport && v . Default != nil {
432+ // Template import should validate the default value.
433+ err := valueIsType (v .Type , * v .Default )
434+ if err != nil {
435+ return "" , diag.Diagnostics {
436+ {
437+ Severity : diag .Error ,
438+ Summary : fmt .Sprintf ("Default value is not of type %q" , v .Type ),
439+ Detail : err .Error (),
440+ AttributePath : defaultValuePath ,
441+ },
442+ }
443+ }
444+
445+ d := v .validValue (* v .Default , optionType , optionValues , defaultValuePath )
446+ if d .HasError () {
447+ return "" , d
448+ }
449+ }
442450
443451 // TODO: Move this into 'Parameter.validValue'. It exists as another check outside because
444452 // the current behavior is to always apply this validation, regardless if the param is set or not.
@@ -453,7 +461,7 @@ func (v *Parameter) Valid(value *string) diag.Diagnostics {
453461 validCheck := & v .Validation [0 ]
454462 err := validCheck .Valid (v .Type , * validVal )
455463 if err != nil {
456- return diag.Diagnostics {
464+ return "" , diag.Diagnostics {
457465 {
458466 Severity : diag .Error ,
459467 Summary : fmt .Sprintf ("Invalid parameter %s according to 'validation' block" , strings .ToLower (v .Name )),
@@ -464,17 +472,26 @@ func (v *Parameter) Valid(value *string) diag.Diagnostics {
464472 }
465473 }
466474
475+ // TODO: This is a bit of a hack. The current behavior states if validation
476+ // is given, then apply validation to unset values.
477+ // This should be removed, and all values should be validated. Meaning
478+ // value == nil should not be accepted in the first place.
479+ if len (v .Validation ) > 0 && value == nil {
480+ empty := ""
481+ value = & empty
482+ }
483+
467484 // Value is only validated if it is set. If it is unset, validation
468485 // is skipped.
469486 if value != nil {
470487 d := v .validValue (* value , optionType , optionValues , cty.Path {})
471488 if d .HasError () {
472- return d
489+ return "" , d
473490 }
474491
475492 err = valueIsType (v .Type , * value )
476493 if err != nil {
477- return diag.Diagnostics {
494+ return "" , diag.Diagnostics {
478495 {
479496 Severity : diag .Error ,
480497 Summary : fmt .Sprintf ("Parameter value is not of type %q" , v .Type ),
@@ -484,7 +501,12 @@ func (v *Parameter) Valid(value *string) diag.Diagnostics {
484501 }
485502 }
486503
487- return nil
504+ if value == nil {
505+ // The previous behavior is to always write an empty string
506+ return "" , nil
507+ }
508+
509+ return * value , nil
488510}
489511
490512func (v * Parameter ) ValidOptions (optionType OptionType ) (map [string ]struct {}, diag.Diagnostics ) {
@@ -598,6 +620,21 @@ func (v *Parameter) validValue(value string, optionType OptionType, optionValues
598620 }
599621 }
600622
623+ if len (v .Validation ) == 1 {
624+ validCheck := & v .Validation [0 ]
625+ err := validCheck .Valid (v .Type , value )
626+ if err != nil {
627+ return diag.Diagnostics {
628+ {
629+ Severity : diag .Error ,
630+ Summary : fmt .Sprintf ("Invalid parameter %s according to 'validation' block" , strings .ToLower (v .Name )),
631+ Detail : err .Error (),
632+ AttributePath : cty.Path {},
633+ },
634+ }
635+ }
636+ }
637+
601638 return nil
602639}
603640
0 commit comments