Skip to content

Commit 9b97e6b

Browse files
committed
prep valid function to inherit from provider repo
1 parent 26db550 commit 9b97e6b

File tree

3 files changed

+97
-32
lines changed

3 files changed

+97
-32
lines changed

extract/parameter.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ func ParameterFromBlock(block *terraform.Block) (*types.Parameter, hcl.Diagnosti
101101
valStr := pVal.Value.AsString()
102102
// Apply validations to the parameter value
103103
for _, v := range p.Validations {
104-
if err := v.Valid(valStr); err != nil {
104+
if err := v.Valid(string(pType), valStr); err != nil {
105105
diags = diags.Append(&hcl.Diagnostic{
106106
Severity: hcl.DiagError,
107107
Summary: fmt.Sprintf("Paramater validation failed for value %q", valStr),

todo.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,23 @@
22

33
## Errors
44

5-
- Dynamic parameter names? Changing names?
6-
- `terraform init` not run before a `preview` fails to load a module. Should this prevent a preview?
5+
- Dynamic parameter names? A parameter's name can change. Should we throw a warning if it is not static?
6+
- [18](https://github.com/coder/preview/issues/18) `terraform init` not run before a `preview` fails to load a module. Should this prevent a preview?
77

88
## Security
99

10-
- Ensure local disk is not accessible from terraform
11-
- Ensure no remote http requests for module fetching
10+
- [19](https://github.com/coder/preview/issues/19) Ensure local disk is not accessible from terraform
11+
- [20](https://github.com/coder/preview/issues/20) Ensure no remote http requests for module fetching
1212

1313
## Performance
1414

15-
- Ensure no remote http requests for module fetching
1615
- Plan hook replaces the same context for every block in a module. This work is duplicated and could be trimmed down.
17-
- Ensure no panics can occur during a preview.
16+
- [21](https://github.com/coder/preview/issues/21) Ensure no panics can occur during a preview.
1817

1918
## Features
2019

2120
- Allow a "force submit" to bypass any `preview` errors. This would defer to the terraform errors (basically the status quo today)
22-
- Errors during the parsing should be reported.
21+
- [22](https://github.com/coder/preview/issues/22) Errors during the parsing should be reported.
2322
- Errors during the hooks should be reported.
2423
- Interactive shell to debug references
2524

@@ -34,15 +33,16 @@
3433
## Verification
3534

3635
- Nested blocks (within 1 module) should have correct context set via plan files. Since plan files are set on the parent, the parent of a sub-block is the incorrect level for a context.
36+
- This might be already correct
3737

3838
## Debt
3939

40-
- Implement `validation` blocks with a common code component to be reused by terraform provider?
40+
- [23](https://github.com/coder/preview/issues/23) Implement `validation` blocks with a common code component to be reused by terraform provider?
4141
- Parameter values/defaults are only `string` types.
4242

4343
## Upstream work
4444

45-
- How will the hooks work if they cannot be merged upstream? Alternative?
45+
- [24](https://github.com/coder/preview/issues/24)How will the hooks work if they cannot be merged upstream? Alternative?
4646
- Load in plan state
4747
- Semantics for parameter coder blocks
4848

@@ -52,8 +52,8 @@
5252

5353
## Bugs
5454

55-
- Submodule references ignored in `count` meta arguments (and dynamic blocks)?
56-
- https://github.com/aquasecurity/trivy/pull/8479
55+
- [25](https://github.com/coder/preview/issues/25) Submodule references ignored in `count` meta arguments (and dynamic blocks)?
56+
- https://github.com/aquasecurity/trivy/pull/8479
5757

5858
## Websocket demo
5959

types/parameter.go

Lines changed: 85 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package types
22

33
import (
4+
"encoding/json"
5+
"errors"
46
"fmt"
57
"regexp"
68
"slices"
@@ -16,6 +18,9 @@ import (
1618
const (
1719
BlockTypeParameter = "coder_parameter"
1820
BlockTypeWorkspaceTag = "coder_workspace_tag"
21+
22+
ValidationMonotonicIncreasing = "increasing"
23+
ValidationMonotonicDecreasing = "decreasing"
1924
)
2025

2126
func SortParameters(lists []Parameter) {
@@ -59,55 +64,106 @@ type RichParameter struct {
5964
}
6065

6166
type ParameterValidation struct {
67+
Error string `json:"validation_error"`
68+
69+
// All validation attributes are optional.
6270
Regex *string `json:"validation_regex"`
63-
Error string `json:"validation_error"`
6471
Min *int64 `json:"validation_min"`
6572
Max *int64 `json:"validation_max"`
6673
Monotonic *string `json:"validation_monotonic"`
6774
}
6875

6976
// TODO: Match implementation from https://github.com/coder/terraform-provider-coder/blob/main/provider/parameter.go#L404-L462
7077
// TODO: Does the value have to be an option from the set of options?
71-
func (v ParameterValidation) Valid(p string) error {
72-
validErr := xerrors.New(v.errorRendered(p))
73-
if v.Regex != nil {
74-
exp, err := regexp.Compile(*v.Regex)
75-
if err != nil {
76-
return fmt.Errorf("invalid regex %q: %w", *v.Regex, err)
78+
79+
// Valid takes the type of the value and the value itself and returns an error
80+
// if the value is invalid.
81+
func (v ParameterValidation) Valid(typ, value string) error {
82+
if typ != "number" {
83+
var cannot []string
84+
if v.Min != nil {
85+
cannot = append(cannot, "min")
86+
}
87+
88+
if v.Max != nil {
89+
cannot = append(cannot, "max")
90+
}
91+
92+
if v.Monotonic != nil {
93+
cannot = append(cannot, "monotonic")
7794
}
7895

79-
if !exp.MatchString(p) {
80-
return validErr
96+
if len(cannot) == 1 {
97+
return fmt.Errorf("field %q is not supported for the type %q", cannot[0], typ)
8198
}
99+
100+
if len(cannot) > 1 {
101+
return fmt.Errorf("fields [%s] are not supported for the type %q", strings.Join(cannot, ", "), typ)
102+
}
103+
}
104+
105+
if typ != "string" && v.Regex != nil {
106+
return fmt.Errorf("field %q is not supported for the type %q", "regex", typ)
82107
}
83108

84-
if v.Min != nil || v.Max != nil {
85-
vd, err := strconv.ParseInt(p, 10, 64)
109+
switch typ {
110+
case "bool":
111+
// Terraform boolean literals are "true" and "false" case-sensitive.
112+
// Do not allow alternate casing.
113+
if value != "true" && value != "false" {
114+
return fmt.Errorf(`boolean value can be either "true" or "false"`)
115+
}
116+
return nil
117+
case "string":
118+
if v.Regex == nil {
119+
return nil
120+
}
121+
122+
exp, err := regexp.Compile(*v.Regex)
86123
if err != nil {
87-
return fmt.Errorf("invalid number value %q: %w", p, err)
124+
return fmt.Errorf("compile regex %q: %s", *v.Regex, err)
88125
}
89126

90-
if v.Min != nil && vd < *v.Min {
91-
return validErr
127+
if v.Error == "" {
128+
return fmt.Errorf("an error must be specified with a regex validation")
92129
}
93130

94-
if v.Max != nil && vd > *v.Max {
95-
return validErr
131+
matched := exp.MatchString(value)
132+
if !matched {
133+
return fmt.Errorf("%s (value %q does not match %q)", v.errorRendered(value), value, exp)
134+
}
135+
case "number":
136+
num, err := strconv.ParseInt(value, 10, 64)
137+
if err != nil {
138+
return takeFirstError(v.errorRendered(value), fmt.Errorf("value %q is not a number", value))
139+
}
140+
if v.Min != nil && num < *v.Min {
141+
return takeFirstError(v.errorRendered(value), fmt.Errorf("value %d is less than the minimum %d", num, v.Min))
142+
}
143+
if v.Max != nil && num > *v.Max {
144+
return takeFirstError(v.errorRendered(value), fmt.Errorf("value %d is more than the maximum %d", num, v.Max))
145+
}
146+
if v.Monotonic != nil && *v.Monotonic != ValidationMonotonicIncreasing && *v.Monotonic != ValidationMonotonicDecreasing {
147+
return fmt.Errorf("number monotonicity can be either %q or %q", ValidationMonotonicIncreasing, ValidationMonotonicDecreasing)
148+
}
149+
case "list(string)":
150+
var listOfStrings []string
151+
err := json.Unmarshal([]byte(value), &listOfStrings)
152+
if err != nil {
153+
return fmt.Errorf("value %q is not valid list of strings", value)
96154
}
97155
}
98156

99-
// Monotonic?
100-
101157
return nil
102158
}
103159

104-
func (v ParameterValidation) errorRendered(value string) string {
160+
func (v ParameterValidation) errorRendered(value string) error {
105161
r := strings.NewReplacer(
106162
"{min}", fmt.Sprintf("%d", safeDeref(v.Min)),
107163
"{max}", fmt.Sprintf("%d", safeDeref(v.Max)),
108164
"{value}", value,
109165
)
110-
return r.Replace(v.Error)
166+
return errors.New(r.Replace(v.Error))
111167
}
112168

113169
type ParameterOption struct {
@@ -141,3 +197,12 @@ func safeDeref[T any](v *T) T {
141197
}
142198
return *v
143199
}
200+
201+
func takeFirstError(errs ...error) error {
202+
for _, err := range errs {
203+
if err != nil {
204+
return err
205+
}
206+
}
207+
return xerrors.Errorf("developer error: error message is not provided")
208+
}

0 commit comments

Comments
 (0)