Skip to content

Commit 60f7cbb

Browse files
authored
chore: rework validation imported from the provider
Validation imported from the provider to match the terraform behavior.
2 parents 6f500ca + 753772a commit 60f7cbb

File tree

13 files changed

+211
-78
lines changed

13 files changed

+211
-78
lines changed

cli/clidisplay/resources.go

+6-13
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ func WorkspaceTags(writer io.Writer, tags types.TagBlocks) hcl.Diagnostics {
4343

4444
func Parameters(writer io.Writer, params []types.Parameter, files map[string]*hcl.File) {
4545
tableWriter := table.NewWriter()
46-
// tableWriter.SetTitle("Parameters")
4746
tableWriter.SetStyle(table.StyleLight)
4847
tableWriter.Style().Options.SeparateColumns = false
4948
row := table.Row{"Parameter"}
@@ -54,20 +53,14 @@ func Parameters(writer io.Writer, params []types.Parameter, files map[string]*hc
5453
if p.FormType == provider.ParameterFormTypeMultiSelect {
5554
_ = json.Unmarshal([]byte(strVal), &selections)
5655
}
57-
// value := p.Value.Value
58-
//
59-
// if value.IsNull() {
60-
// strVal = "null"
61-
// } else if !p.Value.Value.IsKnown() {
62-
// strVal = "unknown"
63-
// } else if value.Type().Equals(cty.String) {
64-
// strVal = value.AsString()
65-
// } else {
66-
// strVal = value.GoString()
67-
//}
56+
57+
dp := p.DisplayName
58+
if p.DisplayName == "" {
59+
dp = p.Name
60+
}
6861

6962
tableWriter.AppendRow(table.Row{
70-
fmt.Sprintf("(%s) %s: %s\n%s", p.DisplayName, p.Name, p.Description, formatOptions(selections, p.Options)),
63+
fmt.Sprintf("(%s) %s: %s\n%s", dp, p.Name, p.Description, formatOptions(selections, p.Options)),
7164
})
7265

7366
if hcl.Diagnostics(p.Diagnostics).HasErrors() {

extract/parameter.go

+19-38
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,12 @@ func ParameterFromBlock(block *terraform.Block) (*types.Parameter, hcl.Diagnosti
5050

5151
pVal := richParameterValue(block)
5252

53-
def := types.StringLiteral("")
53+
requiredValue := true
54+
def := types.NullString()
5455
defAttr := block.GetAttribute("default")
5556
if !defAttr.IsNil() {
5657
def = types.ToHCLString(block, defAttr)
58+
requiredValue = false
5759
}
5860

5961
ftmeta := optionalString(block, "styling")
@@ -77,7 +79,7 @@ func ParameterFromBlock(block *terraform.Block) (*types.Parameter, hcl.Diagnosti
7779
Icon: optionalString(block, "icon"),
7880
Options: make([]*types.ParameterOption, 0),
7981
Validations: make([]*types.ParameterValidation, 0),
80-
Required: optionalBoolean(block, "required"),
82+
Required: requiredValue,
8183
DisplayName: optionalString(block, "display_name"),
8284
Order: optionalInteger(block, "order"),
8385
Ephemeral: optionalBoolean(block, "ephemeral"),
@@ -138,40 +140,10 @@ func ParameterFromBlock(block *terraform.Block) (*types.Parameter, hcl.Diagnosti
138140
p.Validations = append(p.Validations, &valid)
139141
}
140142

141-
ctyType, err := p.CtyType()
142-
if err != nil {
143-
paramTypeDiag := &hcl.Diagnostic{
144-
Severity: hcl.DiagError,
145-
Summary: fmt.Sprintf("Invalid parameter type %q", p.Type),
146-
Detail: err.Error(),
147-
Context: &block.HCLBlock().DefRange,
148-
}
149-
150-
if attr := block.GetAttribute("type"); attr != nil && !attr.IsNil() {
151-
paramTypeDiag.Subject = &attr.HCLAttribute().Range
152-
paramTypeDiag.Expression = attr.HCLAttribute().Expr
153-
paramTypeDiag.EvalContext = block.Context().Inner()
154-
}
155-
diags = diags.Append(paramTypeDiag)
156-
p.FormType = provider.ParameterFormTypeError
157-
}
158-
159-
if ctyType != cty.NilType && pVal.Value.Type().Equals(cty.String) {
160-
// TODO: Wish we could support more types, but only string types are
161-
// allowed.
162-
//nolint:gocritic // string type asserted
163-
valStr := pVal.Value.AsString()
164-
// Apply validations to the parameter value
165-
for _, v := range p.Validations {
166-
if err := v.Valid(string(pType), valStr); err != nil {
167-
diags = diags.Append(&hcl.Diagnostic{
168-
Severity: hcl.DiagError,
169-
Summary: fmt.Sprintf("Paramater validation failed for value %q", valStr),
170-
Detail: err.Error(),
171-
Expression: pVal.ValueExpr,
172-
})
173-
}
174-
}
143+
if !diags.HasErrors() {
144+
// Only do this validation if the parameter is valid, as if some errors
145+
// exist, then this is likely to fail be excess information.
146+
diags = diags.Extend(p.Valid(p.Value))
175147
}
176148

177149
usageDiags := ParameterUsageDiagnostics(p)
@@ -189,7 +161,9 @@ func ParameterFromBlock(block *terraform.Block) (*types.Parameter, hcl.Diagnosti
189161
func ParameterUsageDiagnostics(p types.Parameter) hcl.Diagnostics {
190162
valErr := "The value of a parameter is required to be sourced (default or input) for the parameter to function."
191163
var diags hcl.Diagnostics
192-
if !p.Value.Valid() {
164+
if p.Value.Value.IsNull() {
165+
// Allow null values
166+
} else if !p.Value.Valid() {
193167
diags = diags.Append(&hcl.Diagnostic{
194168
Severity: hcl.DiagError,
195169
Summary: "Parameter value is not valid",
@@ -244,7 +218,6 @@ func ParameterValidationFromBlock(block *terraform.Block) (types.ParameterValida
244218
Min: nullableInteger(block, "min"),
245219
Max: nullableInteger(block, "max"),
246220
Monotonic: nullableString(block, "monotonic"),
247-
Invalid: nullableBoolean(block, "invalid"),
248221
}
249222

250223
return p, diags
@@ -477,6 +450,14 @@ func richParameterValue(block *terraform.Block) types.HCLString {
477450

478451
val, diags := valRef.Value(block.Context().Inner())
479452
source := hclext.CreateDotReferenceFromTraversal(valRef.Traversal)
453+
454+
// If no value attribute exists, then the value is `null`.
455+
if diags.HasErrors() && diags[0].Summary == "Unsupported attribute" {
456+
s := types.NullString()
457+
s.Source = &source
458+
return s
459+
}
460+
480461
return types.HCLString{
481462
Value: val,
482463
ValueDiags: diags,

extract/state.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func ParameterFromState(block *tfjson.StateResource) (types.Parameter, error) {
7474
Icon: st.optionalString("icon"),
7575
Options: options,
7676
Validations: validations,
77-
Required: st.optionalBool("required"),
77+
Required: !st.optionalBool("optional"),
7878
DisplayName: st.optionalString("display_name"),
7979
Order: st.optionalInteger("order"),
8080
Ephemeral: st.optionalBool("ephemeral"),

go.mod

+4-4
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,18 @@ require (
77
github.com/aquasecurity/trivy v0.58.2
88
github.com/coder/guts v1.0.2-0.20250227211802-139809366a22
99
github.com/coder/serpent v0.10.0
10-
github.com/coder/terraform-provider-coder/v2 v2.4.0-pre1.0.20250417100258-c86bb5c3ddcd
10+
github.com/coder/terraform-provider-coder/v2 v2.4.0-pre1.0.20250506184715-e011f733bf27
1111
github.com/coder/websocket v1.8.13
1212
github.com/go-chi/chi v4.1.2+incompatible
13+
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320
1314
github.com/hashicorp/go-version v1.7.0
1415
github.com/hashicorp/hc-install v0.9.2
1516
github.com/hashicorp/hcl/v2 v2.23.0
1617
github.com/hashicorp/terraform-exec v0.23.0
1718
github.com/hashicorp/terraform-json v0.24.0
19+
github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1
1820
github.com/jedib0t/go-pretty/v6 v6.6.7
21+
github.com/quasilyte/go-ruleguard/dsl v0.3.22
1922
github.com/stretchr/testify v1.10.0
2023
github.com/zclconf/go-cty v1.16.2
2124
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da
@@ -69,7 +72,6 @@ require (
6972
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
7073
github.com/hashicorp/errwrap v1.1.0 // indirect
7174
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
72-
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect
7375
github.com/hashicorp/go-getter v1.7.8 // indirect
7476
github.com/hashicorp/go-hclog v1.6.3 // indirect
7577
github.com/hashicorp/go-multierror v1.1.1 // indirect
@@ -79,7 +81,6 @@ require (
7981
github.com/hashicorp/logutils v1.0.0 // indirect
8082
github.com/hashicorp/terraform-plugin-go v0.26.0 // indirect
8183
github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect
82-
github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1 // indirect
8384
github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 // indirect
8485
github.com/klauspost/compress v1.17.11 // indirect
8586
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
@@ -98,7 +99,6 @@ require (
9899
github.com/pion/udp v0.1.4 // indirect
99100
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
100101
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
101-
github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect
102102
github.com/rivo/uniseg v0.4.7 // indirect
103103
github.com/robfig/cron/v3 v3.0.1 // indirect
104104
github.com/samber/lo v1.49.1 // indirect

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -718,8 +718,8 @@ github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 h1:3A0ES21Ke+FxEM8CXx
718718
github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0/go.mod h1:5UuS2Ts+nTToAMeOjNlnHFkPahrtDkmpydBen/3wgZc=
719719
github.com/coder/serpent v0.10.0 h1:ofVk9FJXSek+SmL3yVE3GoArP83M+1tX+H7S4t8BSuM=
720720
github.com/coder/serpent v0.10.0/go.mod h1:cZFW6/fP+kE9nd/oRkEHJpG6sXCtQ+AX7WMMEHv0Y3Q=
721-
github.com/coder/terraform-provider-coder/v2 v2.4.0-pre1.0.20250417100258-c86bb5c3ddcd h1:FsIG6Fd0YOEK7D0Hl/CJywRA+Y6Gd5RQbSIa2L+/BmE=
722-
github.com/coder/terraform-provider-coder/v2 v2.4.0-pre1.0.20250417100258-c86bb5c3ddcd/go.mod h1:56/KdGYaA+VbwXJbTI8CA57XPfnuTxN8rjxbR34PbZw=
721+
github.com/coder/terraform-provider-coder/v2 v2.4.0-pre1.0.20250506184715-e011f733bf27 h1:CLJwMqst39+wfFehYQzVOiG5uXUtC5fbAZ3/EpxOWos=
722+
github.com/coder/terraform-provider-coder/v2 v2.4.0-pre1.0.20250506184715-e011f733bf27/go.mod h1:2kaBpn5k9ZWtgKq5k4JbkVZG9DzEqR4mJSmpdshcO+s=
723723
github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE=
724724
github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
725725
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=

preview_test.go

+10
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,16 @@ func Test_Extract(t *testing.T) {
211211
unknownTags: []string{},
212212
params: map[string]assertParam{},
213213
},
214+
{
215+
name: "empty default",
216+
dir: "emptydefault",
217+
expTags: map[string]string{},
218+
input: preview.Input{},
219+
unknownTags: []string{},
220+
params: map[string]assertParam{
221+
"word": ap(),
222+
},
223+
},
214224
{
215225
name: "many modules",
216226
dir: "manymodules",

site/src/types/preview.ts

-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ export interface ParameterValidation {
7373
readonly validation_min: number | null;
7474
readonly validation_max: number | null;
7575
readonly validation_monotonic: string | null;
76-
readonly validation_invalid: boolean | null;
7776
}
7877

7978
// From web/session.go

testdata/emptydefault/main.tf

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
terraform {
2+
required_providers {
3+
coder = {
4+
source = "coder/coder"
5+
version = "2.4.0-pre0"
6+
}
7+
}
8+
}
9+
10+
data "coder_parameter" "word" {
11+
name = "word"
12+
description = "Select something"
13+
type = "string"
14+
order = 1
15+
# No default selected
16+
17+
option {
18+
name = "Bird"
19+
value = "bird"
20+
description = "An animal that can fly."
21+
}
22+
option {
23+
name = "Boat"
24+
value = "boat"
25+
}
26+
}

testdata/emptydefault/skipe2e

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Skipping until https://github.com/coder/terraform-provider-coder/pull/381 is merged and released

types/convert.go

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package types
2+
3+
import (
4+
"github.com/aquasecurity/trivy/pkg/iac/terraform"
5+
hcty "github.com/hashicorp/go-cty/cty"
6+
"github.com/hashicorp/hcl/v2"
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
8+
9+
"github.com/coder/terraform-provider-coder/v2/provider"
10+
)
11+
12+
func providerValidations(vals []*ParameterValidation) []provider.Validation {
13+
cpy := make([]provider.Validation, 0, len(vals))
14+
for _, val := range vals {
15+
cpy = append(cpy, providerValidation(val))
16+
}
17+
return cpy
18+
}
19+
20+
func providerValidation(v *ParameterValidation) provider.Validation {
21+
return provider.Validation{
22+
Min: int(orZero(v.Min)),
23+
MinDisabled: v.Min == nil,
24+
Max: int(orZero(v.Max)),
25+
MaxDisabled: v.Max == nil,
26+
Monotonic: orZero(v.Monotonic),
27+
Regex: orZero(v.Regex),
28+
Error: v.Error,
29+
}
30+
}
31+
32+
func providerOptions(opts []*ParameterOption) []provider.Option {
33+
cpy := make([]provider.Option, 0, len(opts))
34+
for _, opt := range opts {
35+
cpy = append(cpy, providerOption(opt))
36+
}
37+
return cpy
38+
}
39+
40+
func providerOption(opt *ParameterOption) provider.Option {
41+
return provider.Option{
42+
Name: opt.Name,
43+
Description: opt.Description,
44+
Value: opt.Value.AsString(),
45+
Icon: opt.Icon,
46+
}
47+
}
48+
49+
func hclDiagnostics(diagnostics diag.Diagnostics, source *terraform.Block) hcl.Diagnostics {
50+
cpy := make(hcl.Diagnostics, 0, len(diagnostics))
51+
for _, d := range diagnostics {
52+
cpy = append(cpy, hclDiagnostic(d, source))
53+
}
54+
return cpy
55+
}
56+
57+
func hclDiagnostic(d diag.Diagnostic, source *terraform.Block) *hcl.Diagnostic {
58+
sev := hcl.DiagInvalid
59+
switch d.Severity {
60+
case diag.Error:
61+
sev = hcl.DiagError
62+
case diag.Warning:
63+
sev = hcl.DiagWarning
64+
}
65+
66+
// This is an imperfect way to finding the source code of the error. There is 2
67+
// different `cty` types at place here, the hashicorp fork and the original. So a
68+
// more general solution is difficult. This is good enough for now to add more
69+
// context to an error.
70+
var subject *hcl.Range
71+
if len(d.AttributePath) == 1 && source != nil {
72+
if attr, ok := d.AttributePath[0].(hcty.GetAttrStep); ok {
73+
src := source.GetAttribute(attr.Name)
74+
if src != nil {
75+
subject = &(src.HCLAttribute().Range)
76+
}
77+
}
78+
}
79+
80+
return &hcl.Diagnostic{
81+
Severity: sev,
82+
Summary: d.Summary,
83+
Detail: d.Detail,
84+
Subject: subject,
85+
Context: nil,
86+
Expression: nil,
87+
EvalContext: nil,
88+
Extra: nil,
89+
}
90+
}

types/enum.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ package types
33
import (
44
"fmt"
55
"strings"
6+
7+
"github.com/coder/terraform-provider-coder/v2/provider"
68
)
79

8-
type ParameterType string
10+
// TODO: Just use the provider type directly.
11+
type ParameterType provider.OptionType
912

1013
const (
1114
ParameterTypeString ParameterType = "string"

0 commit comments

Comments
 (0)