Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit8692a56

Browse files
authored
feat: enforce monotonicity in terraform provider (#392)
Previous value must come from env var.
1 parent3c74804 commit8692a56

File tree

3 files changed

+241
-109
lines changed

3 files changed

+241
-109
lines changed

‎provider/parameter.go

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,13 @@ func parameterDataSource() *schema.Resource {
144144
input=&envValue
145145
}
146146

147-
value,diags:=parameter.ValidateInput(input)
147+
varprevious*string
148+
envPreviousValue,ok:=os.LookupEnv(ParameterEnvironmentVariablePrevious(parameter.Name))
149+
ifok {
150+
previous=&envPreviousValue
151+
}
152+
153+
value,diags:=parameter.ValidateInput(input,previous)
148154
ifdiags.HasError() {
149155
returndiags
150156
}
@@ -395,7 +401,7 @@ func valueIsType(typ OptionType, value string) error {
395401
returnnil
396402
}
397403

398-
func (v*Parameter)ValidateInput(input*string) (string, diag.Diagnostics) {
404+
func (v*Parameter)ValidateInput(input*string,previous*string) (string, diag.Diagnostics) {
399405
varerrerror
400406
varoptionTypeOptionType
401407

@@ -442,7 +448,7 @@ func (v *Parameter) ValidateInput(input *string) (string, diag.Diagnostics) {
442448
forcedValue=*value
443449
}
444450

445-
d:=v.validValue(forcedValue,optionType,optionValues,valuePath)
451+
d:=v.validValue(forcedValue,previous,optionType,optionValues,valuePath)
446452
ifd.HasError() {
447453
return"",d
448454
}
@@ -506,7 +512,7 @@ func (v *Parameter) ValidOptions(optionType OptionType) (map[string]struct{}, di
506512
returnoptionValues,nil
507513
}
508514

509-
func (v*Parameter)validValue(valuestring,optionTypeOptionType,optionValuesmap[string]struct{},path cty.Path) diag.Diagnostics {
515+
func (v*Parameter)validValue(valuestring,previous*string,optionTypeOptionType,optionValuesmap[string]struct{},path cty.Path) diag.Diagnostics {
510516
// name is used for constructing more precise error messages.
511517
name:="Value"
512518
ifpath.Equals(defaultValuePath) {
@@ -573,7 +579,7 @@ func (v *Parameter) validValue(value string, optionType OptionType, optionValues
573579

574580
iflen(v.Validation)==1 {
575581
validCheck:=&v.Validation[0]
576-
err:=validCheck.Valid(v.Type,value)
582+
err:=validCheck.Valid(v.Type,value,previous)
577583
iferr!=nil {
578584
return diag.Diagnostics{
579585
{
@@ -589,7 +595,7 @@ func (v *Parameter) validValue(value string, optionType OptionType, optionValues
589595
returnnil
590596
}
591597

592-
func (v*Validation)Valid(typOptionType,valuestring)error {
598+
func (v*Validation)Valid(typOptionType,valuestring,previous*string)error {
593599
iftyp!=OptionTypeNumber {
594600
if!v.MinDisabled {
595601
returnfmt.Errorf("a min cannot be specified for a %s type",typ)
@@ -639,6 +645,34 @@ func (v *Validation) Valid(typ OptionType, value string) error {
639645
ifv.Monotonic!=""&&v.Monotonic!=ValidationMonotonicIncreasing&&v.Monotonic!=ValidationMonotonicDecreasing {
640646
returnfmt.Errorf("number monotonicity can be either %q or %q",ValidationMonotonicIncreasing,ValidationMonotonicDecreasing)
641647
}
648+
649+
switchv.Monotonic {
650+
case"":
651+
// No monotonicity check
652+
caseValidationMonotonicIncreasing,ValidationMonotonicDecreasing:
653+
ifprevious!=nil {// Only check if previous value exists
654+
previousNum,err:=strconv.Atoi(*previous)
655+
iferr!=nil {
656+
// Do not throw an error for the previous value not being a number. Throwing an
657+
// error here would cause an unrepairable state for the user. This is
658+
// unfortunate, but there is not much we can do at this point.
659+
// TODO: Maybe we should enforce this, and have the calling coderd
660+
// do something to resolve it. Such as doing this check before calling
661+
// terraform apply.
662+
break
663+
}
664+
665+
ifv.Monotonic==ValidationMonotonicIncreasing&&!(num>=previousNum) {
666+
returnfmt.Errorf("parameter value '%d' must be equal or greater than previous value: %d",num,previousNum)
667+
}
668+
669+
ifv.Monotonic==ValidationMonotonicDecreasing&&!(num<=previousNum) {
670+
returnfmt.Errorf("parameter value '%d' must be equal or lower than previous value: %d",num,previousNum)
671+
}
672+
}
673+
default:
674+
returnfmt.Errorf("number monotonicity can be either %q or %q",ValidationMonotonicIncreasing,ValidationMonotonicDecreasing)
675+
}
642676
caseOptionTypeListString:
643677
varlistOfStrings []string
644678
err:=json.Unmarshal([]byte(value),&listOfStrings)
@@ -666,6 +700,15 @@ func ParameterEnvironmentVariable(name string) string {
666700
return"CODER_PARAMETER_"+hex.EncodeToString(sum[:])
667701
}
668702

703+
// ParameterEnvironmentVariablePrevious returns the environment variable to
704+
// specify for a parameter's previous value. This is used for workspace
705+
// subsequent builds after the first. Primarily to validate monotonicity in the
706+
// `validation` block.
707+
funcParameterEnvironmentVariablePrevious(namestring)string {
708+
sum:=sha256.Sum256([]byte(name))
709+
return"CODER_PARAMETER_PREVIOUS_"+hex.EncodeToString(sum[:])
710+
}
711+
669712
functakeFirstError(errs...error)error {
670713
for_,err:=rangeerrs {
671714
iferr!=nil {

‎provider/parameter_test.go

Lines changed: 97 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -839,7 +839,7 @@ func TestParameterValidation(t *testing.T) {
839839
t.Run(tc.Name,func(t*testing.T) {
840840
t.Parallel()
841841
value:=&tc.Value
842-
_,diags:=tc.Parameter.ValidateInput(value)
842+
_,diags:=tc.Parameter.ValidateInput(value,nil)
843843
iftc.ExpectError!=nil {
844844
require.True(t,diags.HasError())
845845
errMsg:=fmt.Sprintf("%+v",diags[0])// close enough
@@ -881,6 +881,7 @@ func TestParameterValidationEnforcement(t *testing.T) {
881881
OutputValuestring
882882
Optionalbool
883883
CreateError*regexp.Regexp
884+
Previous*string
884885
}
885886

886887
rows:=make([]row,0)
@@ -898,33 +899,44 @@ func TestParameterValidationEnforcement(t *testing.T) {
898899
continue// Skip rows with empty names
899900
}
900901

901-
optional,err:=strconv.ParseBool(columns[8])
902-
ifcolumns[8]!="" {
902+
cname,ctype,cprev,cinput,cdefault,coptions,cvalidation,_,coutput,coptional,cerr:=
903+
columns[0],columns[1],columns[2],columns[3],columns[4],columns[5],columns[6],columns[7],columns[8],columns[9],columns[10]
904+
905+
optional,err:=strconv.ParseBool(coptional)
906+
ifcoptional!="" {
903907
// Value does not matter if not specified
904908
require.NoError(t,err)
905909
}
906910

907911
varrerr*regexp.Regexp
908-
ifcolumns[9]!="" {
909-
rerr,err=regexp.Compile(columns[9])
912+
ifcerr!="" {
913+
rerr,err=regexp.Compile(cerr)
910914
iferr!=nil {
911-
t.Fatalf("failed to parse error column %q: %v",columns[9],err)
915+
t.Fatalf("failed to parse error column %q: %v",cerr,err)
912916
}
913917
}
914918

915919
varoptions []string
916-
ifcolumns[4]!="" {
917-
options=strings.Split(columns[4],",")
920+
ifcoptions!="" {
921+
options=strings.Split(coptions,",")
918922
}
919923

920924
varvalidation*provider.Validation
921-
ifcolumns[5]!="" {
922-
// Min-Max validation should look like:
923-
//1-10 :: min=1, max=10
924-
//-10 :: max=10
925-
//1- :: min=1
926-
ifvalidMinMax.MatchString(columns[5]) {
927-
parts:=strings.Split(columns[5],"-")
925+
ifcvalidation!="" {
926+
switch {
927+
casecvalidation==provider.ValidationMonotonicIncreasing||cvalidation==provider.ValidationMonotonicDecreasing:
928+
validation=&provider.Validation{
929+
MinDisabled:true,
930+
MaxDisabled:true,
931+
Monotonic:cvalidation,
932+
Error:"monotonicity",
933+
}
934+
casevalidMinMax.MatchString(cvalidation):
935+
// Min-Max validation should look like:
936+
//1-10 :: min=1, max=10
937+
//-10 :: max=10
938+
//1- :: min=1
939+
parts:=strings.Split(cvalidation,"-")
928940
min,_:=strconv.ParseInt(parts[0],10,64)
929941
max,_:=strconv.ParseInt(parts[1],10,64)
930942
validation=&provider.Validation{
@@ -936,29 +948,37 @@ func TestParameterValidationEnforcement(t *testing.T) {
936948
Regex:"",
937949
Error:"{min} < {value} < {max}",
938950
}
939-
}else {
951+
default:
940952
validation=&provider.Validation{
941953
Min:0,
942954
MinDisabled:true,
943955
Max:0,
944956
MaxDisabled:true,
945957
Monotonic:"",
946-
Regex:columns[5],
958+
Regex:cvalidation,
947959
Error:"regex error",
948960
}
949961
}
950962
}
951963

964+
varprev*string
965+
ifcprev!="" {
966+
prev=ptr(cprev)
967+
ifcprev==`""` {
968+
prev=ptr("")
969+
}
970+
}
952971
rows=append(rows,row{
953-
Name:columns[0],
954-
Types:strings.Split(columns[1],","),
955-
InputValue:columns[2],
956-
Default:columns[3],
972+
Name:cname,
973+
Types:strings.Split(ctype,","),
974+
InputValue:cinput,
975+
Default:cdefault,
957976
Options:options,
958977
Validation:validation,
959-
OutputValue:columns[7],
978+
OutputValue:coutput,
960979
Optional:optional,
961980
CreateError:rerr,
981+
Previous:prev,
962982
})
963983
}
964984

@@ -976,6 +996,9 @@ func TestParameterValidationEnforcement(t *testing.T) {
976996
ifrow.InputValue!="" {
977997
t.Setenv(provider.ParameterEnvironmentVariable("parameter"),row.InputValue)
978998
}
999+
ifrow.Previous!=nil {
1000+
t.Setenv(provider.ParameterEnvironmentVariablePrevious("parameter"),*row.Previous)
1001+
}
9791002

9801003
ifrow.CreateError!=nil&&row.OutputValue!="" {
9811004
t.Errorf("output value %q should not be set if both errors are set",row.OutputValue)
@@ -1067,6 +1090,7 @@ func TestValueValidatesType(t *testing.T) {
10671090
Namestring
10681091
Type provider.OptionType
10691092
Valuestring
1093+
Previous*string
10701094
Regexstring
10711095
RegexErrorstring
10721096
Minint
@@ -1154,6 +1178,56 @@ func TestValueValidatesType(t *testing.T) {
11541178
Min:0,
11551179
Max:2,
11561180
Monotonic:"decreasing",
1181+
}, {
1182+
Name:"IncreasingMonotonicityEqual",
1183+
Type:"number",
1184+
Previous:ptr("1"),
1185+
Value:"1",
1186+
Monotonic:"increasing",
1187+
MinDisabled:true,
1188+
MaxDisabled:true,
1189+
}, {
1190+
Name:"DecreasingMonotonicityEqual",
1191+
Type:"number",
1192+
Value:"1",
1193+
Previous:ptr("1"),
1194+
Monotonic:"decreasing",
1195+
MinDisabled:true,
1196+
MaxDisabled:true,
1197+
}, {
1198+
Name:"IncreasingMonotonicityGreater",
1199+
Type:"number",
1200+
Previous:ptr("0"),
1201+
Value:"1",
1202+
Monotonic:"increasing",
1203+
MinDisabled:true,
1204+
MaxDisabled:true,
1205+
}, {
1206+
Name:"DecreasingMonotonicityGreater",
1207+
Type:"number",
1208+
Value:"1",
1209+
Previous:ptr("0"),
1210+
Monotonic:"decreasing",
1211+
MinDisabled:true,
1212+
MaxDisabled:true,
1213+
Error:regexp.MustCompile("must be equal or"),
1214+
}, {
1215+
Name:"IncreasingMonotonicityLesser",
1216+
Type:"number",
1217+
Previous:ptr("2"),
1218+
Value:"1",
1219+
Monotonic:"increasing",
1220+
MinDisabled:true,
1221+
MaxDisabled:true,
1222+
Error:regexp.MustCompile("must be equal or"),
1223+
}, {
1224+
Name:"DecreasingMonotonicityLesser",
1225+
Type:"number",
1226+
Value:"1",
1227+
Previous:ptr("2"),
1228+
Monotonic:"decreasing",
1229+
MinDisabled:true,
1230+
MaxDisabled:true,
11571231
}, {
11581232
Name:"ValidListOfStrings",
11591233
Type:"list(string)",
@@ -1205,7 +1279,7 @@ func TestValueValidatesType(t *testing.T) {
12051279
Regex:tc.Regex,
12061280
Error:tc.RegexError,
12071281
}
1208-
err:=v.Valid(tc.Type,tc.Value)
1282+
err:=v.Valid(tc.Type,tc.Value,tc.Previous)
12091283
iftc.Error!=nil {
12101284
require.Error(t,err)
12111285
require.True(t,tc.Error.MatchString(err.Error()),"got: %s",err.Error())

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp