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

feat: enforce monotonicity in terraform provider#392

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Emyrk merged 3 commits intomainfromstevenmasley/monotonic
May 8, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 49 additions & 6 deletionsprovider/parameter.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -144,7 +144,13 @@ func parameterDataSource() *schema.Resource {
input = &envValue
}

value, diags := parameter.ValidateInput(input)
var previous *string
envPreviousValue, ok := os.LookupEnv(ParameterEnvironmentVariablePrevious(parameter.Name))
if ok {
previous = &envPreviousValue
}

value, diags := parameter.ValidateInput(input, previous)
if diags.HasError() {
return diags
}
Expand DownExpand Up@@ -395,7 +401,7 @@ func valueIsType(typ OptionType, value string) error {
return nil
}

func (v *Parameter) ValidateInput(input *string) (string, diag.Diagnostics) {
func (v *Parameter) ValidateInput(input *string, previous *string) (string, diag.Diagnostics) {
var err error
var optionType OptionType

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

d := v.validValue(forcedValue, optionType, optionValues, valuePath)
d := v.validValue(forcedValue,previous,optionType, optionValues, valuePath)
if d.HasError() {
return "", d
}
Expand DownExpand Up@@ -506,7 +512,7 @@ func (v *Parameter) ValidOptions(optionType OptionType) (map[string]struct{}, di
return optionValues, nil
}

func (v *Parameter) validValue(value string, optionType OptionType, optionValues map[string]struct{}, path cty.Path) diag.Diagnostics {
func (v *Parameter) validValue(value string,previous *string,optionType OptionType, optionValues map[string]struct{}, path cty.Path) diag.Diagnostics {
// name is used for constructing more precise error messages.
name := "Value"
if path.Equals(defaultValuePath) {
Expand DownExpand Up@@ -573,7 +579,7 @@ func (v *Parameter) validValue(value string, optionType OptionType, optionValues

if len(v.Validation) == 1 {
validCheck := &v.Validation[0]
err := validCheck.Valid(v.Type, value)
err := validCheck.Valid(v.Type, value, previous)
if err != nil {
return diag.Diagnostics{
{
Expand All@@ -589,7 +595,7 @@ func (v *Parameter) validValue(value string, optionType OptionType, optionValues
return nil
}

func (v *Validation) Valid(typ OptionType, value string) error {
func (v *Validation) Valid(typ OptionType, value string, previous *string) error {
if typ != OptionTypeNumber {
if !v.MinDisabled {
return fmt.Errorf("a min cannot be specified for a %s type", typ)
Expand DownExpand Up@@ -639,6 +645,34 @@ func (v *Validation) Valid(typ OptionType, value string) error {
if v.Monotonic != "" && v.Monotonic != ValidationMonotonicIncreasing && v.Monotonic != ValidationMonotonicDecreasing {
return fmt.Errorf("number monotonicity can be either %q or %q", ValidationMonotonicIncreasing, ValidationMonotonicDecreasing)
}

switch v.Monotonic {
case "":
// No monotonicity check
case ValidationMonotonicIncreasing, ValidationMonotonicDecreasing:
if previous != nil { // Only check if previous value exists
previousNum, err := strconv.Atoi(*previous)
if err != nil {
// Do not throw an error for the previous value not being a number. Throwing an
// error here would cause an unrepairable state for the user. This is
// unfortunate, but there is not much we can do at this point.
// TODO: Maybe we should enforce this, and have the calling coderd
// do something to resolve it. Such as doing this check before calling
// terraform apply.
break
}

if v.Monotonic == ValidationMonotonicIncreasing && !(num >= previousNum) {
return fmt.Errorf("parameter value '%d' must be equal or greater than previous value: %d", num, previousNum)
}

if v.Monotonic == ValidationMonotonicDecreasing && !(num <= previousNum) {
return fmt.Errorf("parameter value '%d' must be equal or lower than previous value: %d", num, previousNum)
}
}
default:
return fmt.Errorf("number monotonicity can be either %q or %q", ValidationMonotonicIncreasing, ValidationMonotonicDecreasing)
}
case OptionTypeListString:
var listOfStrings []string
err := json.Unmarshal([]byte(value), &listOfStrings)
Expand DownExpand Up@@ -666,6 +700,15 @@ func ParameterEnvironmentVariable(name string) string {
return "CODER_PARAMETER_" + hex.EncodeToString(sum[:])
}

// ParameterEnvironmentVariablePrevious returns the environment variable to
// specify for a parameter's previous value. This is used for workspace
// subsequent builds after the first. Primarily to validate monotonicity in the
// `validation` block.
func ParameterEnvironmentVariablePrevious(name string) string {
sum := sha256.Sum256([]byte(name))
return "CODER_PARAMETER_PREVIOUS_" + hex.EncodeToString(sum[:])
}

func takeFirstError(errs ...error) error {
for _, err := range errs {
if err != nil {
Expand Down
120 changes: 97 additions & 23 deletionsprovider/parameter_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -839,7 +839,7 @@ func TestParameterValidation(t *testing.T) {
t.Run(tc.Name, func(t *testing.T) {
t.Parallel()
value := &tc.Value
_, diags := tc.Parameter.ValidateInput(value)
_, diags := tc.Parameter.ValidateInput(value, nil)
if tc.ExpectError != nil {
require.True(t, diags.HasError())
errMsg := fmt.Sprintf("%+v", diags[0]) // close enough
Expand DownExpand Up@@ -881,6 +881,7 @@ func TestParameterValidationEnforcement(t *testing.T) {
OutputValue string
Optional bool
CreateError *regexp.Regexp
Previous *string
}

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

optional, err := strconv.ParseBool(columns[8])
if columns[8] != "" {
cname, ctype, cprev, cinput, cdefault, coptions, cvalidation, _, coutput, coptional, cerr :=
columns[0], columns[1], columns[2], columns[3], columns[4], columns[5], columns[6], columns[7], columns[8], columns[9], columns[10]

optional, err := strconv.ParseBool(coptional)
if coptional != "" {
// Value does not matter if not specified
require.NoError(t, err)
}

var rerr *regexp.Regexp
ifcolumns[9] != "" {
rerr, err = regexp.Compile(columns[9])
ifcerr != "" {
rerr, err = regexp.Compile(cerr)
if err != nil {
t.Fatalf("failed to parse error column %q: %v",columns[9], err)
t.Fatalf("failed to parse error column %q: %v",cerr, err)
}
}

var options []string
ifcolumns[4] != "" {
options = strings.Split(columns[4], ",")
ifcoptions != "" {
options = strings.Split(coptions, ",")
}

var validation *provider.Validation
if columns[5] != "" {
// Min-Max validation should look like:
//1-10 :: min=1, max=10
//-10 :: max=10
//1- :: min=1
if validMinMax.MatchString(columns[5]) {
parts := strings.Split(columns[5], "-")
if cvalidation != "" {
switch {
case cvalidation == provider.ValidationMonotonicIncreasing || cvalidation == provider.ValidationMonotonicDecreasing:
validation = &provider.Validation{
MinDisabled: true,
MaxDisabled: true,
Monotonic: cvalidation,
Error: "monotonicity",
}
case validMinMax.MatchString(cvalidation):
// Min-Max validation should look like:
//1-10 :: min=1, max=10
//-10 :: max=10
//1- :: min=1
parts := strings.Split(cvalidation, "-")
min, _ := strconv.ParseInt(parts[0], 10, 64)
max, _ := strconv.ParseInt(parts[1], 10, 64)
validation = &provider.Validation{
Expand All@@ -936,29 +948,37 @@ func TestParameterValidationEnforcement(t *testing.T) {
Regex: "",
Error: "{min} < {value} < {max}",
}
} else {
default:
validation = &provider.Validation{
Min: 0,
MinDisabled: true,
Max: 0,
MaxDisabled: true,
Monotonic: "",
Regex:columns[5],
Regex:cvalidation,
Error: "regex error",
}
}
}

var prev *string
if cprev != "" {
prev = ptr(cprev)
if cprev == `""` {
prev = ptr("")
}
}
rows = append(rows, row{
Name:columns[0],
Types: strings.Split(columns[1], ","),
InputValue:columns[2],
Default:columns[3],
Name:cname,
Types: strings.Split(ctype, ","),
InputValue:cinput,
Default:cdefault,
Options: options,
Validation: validation,
OutputValue:columns[7],
OutputValue:coutput,
Optional: optional,
CreateError: rerr,
Previous: prev,
})
}

Expand All@@ -976,6 +996,9 @@ func TestParameterValidationEnforcement(t *testing.T) {
if row.InputValue != "" {
t.Setenv(provider.ParameterEnvironmentVariable("parameter"), row.InputValue)
}
if row.Previous != nil {
t.Setenv(provider.ParameterEnvironmentVariablePrevious("parameter"), *row.Previous)
}

if row.CreateError != nil && row.OutputValue != "" {
t.Errorf("output value %q should not be set if both errors are set", row.OutputValue)
Expand DownExpand Up@@ -1067,6 +1090,7 @@ func TestValueValidatesType(t *testing.T) {
Name string
Type provider.OptionType
Value string
Previous *string
Regex string
RegexError string
Min int
Expand DownExpand Up@@ -1154,6 +1178,56 @@ func TestValueValidatesType(t *testing.T) {
Min: 0,
Max: 2,
Monotonic: "decreasing",
}, {
Name: "IncreasingMonotonicityEqual",
Type: "number",
Previous: ptr("1"),
Value: "1",
Monotonic: "increasing",
MinDisabled: true,
MaxDisabled: true,
}, {
Name: "DecreasingMonotonicityEqual",
Type: "number",
Value: "1",
Previous: ptr("1"),
Monotonic: "decreasing",
MinDisabled: true,
MaxDisabled: true,
}, {
Name: "IncreasingMonotonicityGreater",
Type: "number",
Previous: ptr("0"),
Value: "1",
Monotonic: "increasing",
MinDisabled: true,
MaxDisabled: true,
}, {
Name: "DecreasingMonotonicityGreater",
Type: "number",
Value: "1",
Previous: ptr("0"),
Monotonic: "decreasing",
MinDisabled: true,
MaxDisabled: true,
Error: regexp.MustCompile("must be equal or"),
}, {
Name: "IncreasingMonotonicityLesser",
Type: "number",
Previous: ptr("2"),
Value: "1",
Monotonic: "increasing",
MinDisabled: true,
MaxDisabled: true,
Error: regexp.MustCompile("must be equal or"),
}, {
Name: "DecreasingMonotonicityLesser",
Type: "number",
Value: "1",
Previous: ptr("2"),
Monotonic: "decreasing",
MinDisabled: true,
MaxDisabled: true,
}, {
Name: "ValidListOfStrings",
Type: "list(string)",
Expand DownExpand Up@@ -1205,7 +1279,7 @@ func TestValueValidatesType(t *testing.T) {
Regex: tc.Regex,
Error: tc.RegexError,
}
err := v.Valid(tc.Type, tc.Value)
err := v.Valid(tc.Type, tc.Value, tc.Previous)
if tc.Error != nil {
require.Error(t, err)
require.True(t, tc.Error.MatchString(err.Error()), "got: %s", err.Error())
Expand Down
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp