Skip to content

chore: rework validation imported from the provider #78

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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
May 7, 2025
Merged
Show file tree
Hide file tree
Changes from 16 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
19 changes: 6 additions & 13 deletions cli/clidisplay/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ func WorkspaceTags(writer io.Writer, tags types.TagBlocks) hcl.Diagnostics {

func Parameters(writer io.Writer, params []types.Parameter, files map[string]*hcl.File) {
tableWriter := table.NewWriter()
// tableWriter.SetTitle("Parameters")
tableWriter.SetStyle(table.StyleLight)
tableWriter.Style().Options.SeparateColumns = false
row := table.Row{"Parameter"}
Expand All @@ -54,20 +53,14 @@ func Parameters(writer io.Writer, params []types.Parameter, files map[string]*hc
if p.FormType == provider.ParameterFormTypeMultiSelect {
_ = json.Unmarshal([]byte(strVal), &selections)
}
// value := p.Value.Value
//
// if value.IsNull() {
// strVal = "null"
// } else if !p.Value.Value.IsKnown() {
// strVal = "unknown"
// } else if value.Type().Equals(cty.String) {
// strVal = value.AsString()
// } else {
// strVal = value.GoString()
//}

dp := p.DisplayName
if p.DisplayName == "" {
dp = p.Name
}

tableWriter.AppendRow(table.Row{
fmt.Sprintf("(%s) %s: %s\n%s", p.DisplayName, p.Name, p.Description, formatOptions(selections, p.Options)),
fmt.Sprintf("(%s) %s: %s\n%s", dp, p.Name, p.Description, formatOptions(selections, p.Options)),
})

if hcl.Diagnostics(p.Diagnostics).HasErrors() {
Expand Down
57 changes: 19 additions & 38 deletions extract/parameter.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,12 @@ func ParameterFromBlock(block *terraform.Block) (*types.Parameter, hcl.Diagnosti

pVal := richParameterValue(block)

def := types.StringLiteral("")
requiredValue := true
def := types.NullString()
defAttr := block.GetAttribute("default")
if !defAttr.IsNil() {
def = types.ToHCLString(block, defAttr)
requiredValue = false
}

ftmeta := optionalString(block, "styling")
Expand All @@ -77,7 +79,7 @@ func ParameterFromBlock(block *terraform.Block) (*types.Parameter, hcl.Diagnosti
Icon: optionalString(block, "icon"),
Options: make([]*types.ParameterOption, 0),
Validations: make([]*types.ParameterValidation, 0),
Required: optionalBoolean(block, "required"),
Required: requiredValue,
DisplayName: optionalString(block, "display_name"),
Order: optionalInteger(block, "order"),
Ephemeral: optionalBoolean(block, "ephemeral"),
Expand Down Expand Up @@ -138,40 +140,10 @@ func ParameterFromBlock(block *terraform.Block) (*types.Parameter, hcl.Diagnosti
p.Validations = append(p.Validations, &valid)
}

ctyType, err := p.CtyType()
if err != nil {
paramTypeDiag := &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Invalid parameter type %q", p.Type),
Detail: err.Error(),
Context: &block.HCLBlock().DefRange,
}

if attr := block.GetAttribute("type"); attr != nil && !attr.IsNil() {
paramTypeDiag.Subject = &attr.HCLAttribute().Range
paramTypeDiag.Expression = attr.HCLAttribute().Expr
paramTypeDiag.EvalContext = block.Context().Inner()
}
diags = diags.Append(paramTypeDiag)
p.FormType = provider.ParameterFormTypeError
}

if ctyType != cty.NilType && pVal.Value.Type().Equals(cty.String) {
// TODO: Wish we could support more types, but only string types are
// allowed.
//nolint:gocritic // string type asserted
valStr := pVal.Value.AsString()
// Apply validations to the parameter value
for _, v := range p.Validations {
if err := v.Valid(string(pType), valStr); err != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Paramater validation failed for value %q", valStr),
Detail: err.Error(),
Expression: pVal.ValueExpr,
})
}
}
if !diags.HasErrors() {
// Only do this validation if the parameter is valid, as if some errors
// exist, then this is likely to fail be excess information.
diags = diags.Extend(p.Valid(p.Value))
}

usageDiags := ParameterUsageDiagnostics(p)
Expand All @@ -189,7 +161,9 @@ func ParameterFromBlock(block *terraform.Block) (*types.Parameter, hcl.Diagnosti
func ParameterUsageDiagnostics(p types.Parameter) hcl.Diagnostics {
valErr := "The value of a parameter is required to be sourced (default or input) for the parameter to function."
var diags hcl.Diagnostics
if !p.Value.Valid() {
if p.Value.Value.IsNull() {
// Allow null values
} else if !p.Value.Valid() {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Parameter value is not valid",
Expand Down Expand Up @@ -244,7 +218,6 @@ func ParameterValidationFromBlock(block *terraform.Block) (types.ParameterValida
Min: nullableInteger(block, "min"),
Max: nullableInteger(block, "max"),
Monotonic: nullableString(block, "monotonic"),
Invalid: nullableBoolean(block, "invalid"),
}

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

val, diags := valRef.Value(block.Context().Inner())
source := hclext.CreateDotReferenceFromTraversal(valRef.Traversal)

// If no value attribute exists, then the value is `null`.
if diags.HasErrors() && diags[0].Summary == "Unsupported attribute" {
s := types.NullString()
s.Source = &source
return s
}

return types.HCLString{
Value: val,
ValueDiags: diags,
Expand Down
2 changes: 1 addition & 1 deletion extract/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func ParameterFromState(block *tfjson.StateResource) (types.Parameter, error) {
Icon: st.optionalString("icon"),
Options: options,
Validations: validations,
Required: st.optionalBool("required"),
Required: !st.optionalBool("optional"),
DisplayName: st.optionalString("display_name"),
Order: st.optionalInteger("order"),
Ephemeral: st.optionalBool("ephemeral"),
Expand Down
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,18 @@ require (
github.com/aquasecurity/trivy v0.58.2
github.com/coder/guts v1.0.2-0.20250227211802-139809366a22
github.com/coder/serpent v0.10.0
github.com/coder/terraform-provider-coder/v2 v2.4.0-pre1.0.20250417100258-c86bb5c3ddcd
github.com/coder/terraform-provider-coder/v2 v2.4.0-pre1.0.20250506184715-e011f733bf27
github.com/coder/websocket v1.8.13
github.com/go-chi/chi v4.1.2+incompatible
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320
github.com/hashicorp/go-version v1.7.0
github.com/hashicorp/hc-install v0.9.2
github.com/hashicorp/hcl/v2 v2.23.0
github.com/hashicorp/terraform-exec v0.23.0
github.com/hashicorp/terraform-json v0.24.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1
github.com/jedib0t/go-pretty/v6 v6.6.7
github.com/quasilyte/go-ruleguard/dsl v0.3.22
github.com/stretchr/testify v1.10.0
github.com/zclconf/go-cty v1.16.2
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da
Expand Down Expand Up @@ -69,7 +72,6 @@ require (
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect
github.com/hashicorp/go-getter v1.7.8 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
Expand All @@ -79,7 +81,6 @@ require (
github.com/hashicorp/logutils v1.0.0 // indirect
github.com/hashicorp/terraform-plugin-go v0.26.0 // indirect
github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect
github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1 // indirect
github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
Expand All @@ -98,7 +99,6 @@ require (
github.com/pion/udp v0.1.4 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/samber/lo v1.49.1 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -718,8 +718,8 @@ github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 h1:3A0ES21Ke+FxEM8CXx
github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0/go.mod h1:5UuS2Ts+nTToAMeOjNlnHFkPahrtDkmpydBen/3wgZc=
github.com/coder/serpent v0.10.0 h1:ofVk9FJXSek+SmL3yVE3GoArP83M+1tX+H7S4t8BSuM=
github.com/coder/serpent v0.10.0/go.mod h1:cZFW6/fP+kE9nd/oRkEHJpG6sXCtQ+AX7WMMEHv0Y3Q=
github.com/coder/terraform-provider-coder/v2 v2.4.0-pre1.0.20250417100258-c86bb5c3ddcd h1:FsIG6Fd0YOEK7D0Hl/CJywRA+Y6Gd5RQbSIa2L+/BmE=
github.com/coder/terraform-provider-coder/v2 v2.4.0-pre1.0.20250417100258-c86bb5c3ddcd/go.mod h1:56/KdGYaA+VbwXJbTI8CA57XPfnuTxN8rjxbR34PbZw=
github.com/coder/terraform-provider-coder/v2 v2.4.0-pre1.0.20250506184715-e011f733bf27 h1:CLJwMqst39+wfFehYQzVOiG5uXUtC5fbAZ3/EpxOWos=
github.com/coder/terraform-provider-coder/v2 v2.4.0-pre1.0.20250506184715-e011f733bf27/go.mod h1:2kaBpn5k9ZWtgKq5k4JbkVZG9DzEqR4mJSmpdshcO+s=
github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE=
github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
Expand Down
10 changes: 10 additions & 0 deletions preview_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,16 @@ func Test_Extract(t *testing.T) {
unknownTags: []string{},
params: map[string]assertParam{},
},
{
name: "empty default",
dir: "emptydefault",
expTags: map[string]string{},
input: preview.Input{},
unknownTags: []string{},
params: map[string]assertParam{
"word": ap(),
},
},
{
name: "many modules",
dir: "manymodules",
Expand Down
1 change: 0 additions & 1 deletion site/src/types/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ export interface ParameterValidation {
readonly validation_min: number | null;
readonly validation_max: number | null;
readonly validation_monotonic: string | null;
readonly validation_invalid: boolean | null;
}

// From web/session.go
Expand Down
26 changes: 26 additions & 0 deletions testdata/emptydefault/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
terraform {
required_providers {
coder = {
source = "coder/coder"
version = "2.4.0-pre0"
}
}
}

data "coder_parameter" "word" {
name = "word"
description = "Select something"
type = "string"
order = 1
# No default selected

option {
name = "Bird"
value = "bird"
description = "An animal that can fly."
}
option {
name = "Boat"
value = "boat"
}
}
1 change: 1 addition & 0 deletions testdata/emptydefault/skipe2e
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Skipping until https://github.com/coder/terraform-provider-coder/pull/381 is merged and released
90 changes: 90 additions & 0 deletions types/convert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package types

import (
"github.com/aquasecurity/trivy/pkg/iac/terraform"
hcty "github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"

"github.com/coder/terraform-provider-coder/v2/provider"
)

func providerValidations(vals []*ParameterValidation) []provider.Validation {
cpy := make([]provider.Validation, 0, len(vals))
for _, val := range vals {
cpy = append(cpy, providerValidation(val))
}
return cpy
}

func providerValidation(v *ParameterValidation) provider.Validation {
return provider.Validation{
Min: int(orZero(v.Min)),
MinDisabled: v.Min == nil,
Max: int(orZero(v.Max)),
MaxDisabled: v.Max == nil,
Monotonic: orZero(v.Monotonic),
Regex: orZero(v.Regex),
Error: v.Error,
}
}

func providerOptions(opts []*ParameterOption) []provider.Option {
cpy := make([]provider.Option, 0, len(opts))
for _, opt := range opts {
cpy = append(cpy, providerOption(opt))
}
return cpy
}

func providerOption(opt *ParameterOption) provider.Option {
return provider.Option{
Name: opt.Name,
Description: opt.Description,
Value: opt.Value.AsString(),
Icon: opt.Icon,
}
}

func hclDiagnostics(diagnostics diag.Diagnostics, source *terraform.Block) hcl.Diagnostics {
cpy := make(hcl.Diagnostics, 0, len(diagnostics))
for _, d := range diagnostics {
cpy = append(cpy, hclDiagnostic(d, source))
}
return cpy
}

func hclDiagnostic(d diag.Diagnostic, source *terraform.Block) *hcl.Diagnostic {
sev := hcl.DiagInvalid
switch d.Severity {
case diag.Error:
sev = hcl.DiagError
case diag.Warning:
sev = hcl.DiagWarning
}

// This is an imperfect way to finding the source code of the error. There is 2
// different `cty` types at place here, the hashicorp fork and the original. So a
// more general solution is difficult. This is good enough for now to add more
// context to an error.
var subject *hcl.Range
if len(d.AttributePath) == 1 && source != nil {
if attr, ok := d.AttributePath[0].(hcty.GetAttrStep); ok {
src := source.GetAttribute(attr.Name)
if src != nil {
subject = &(src.HCLAttribute().Range)
}
}
}

return &hcl.Diagnostic{
Severity: sev,
Summary: d.Summary,
Detail: d.Detail,
Subject: subject,
Context: nil,
Expression: nil,
EvalContext: nil,
Extra: nil,
}
}
5 changes: 4 additions & 1 deletion types/enum.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ package types
import (
"fmt"
"strings"

"github.com/coder/terraform-provider-coder/v2/provider"
)

type ParameterType string
// TODO: Just use the provider type directly.
type ParameterType provider.OptionType

const (
ParameterTypeString ParameterType = "string"
Expand Down
Loading
Loading