From 625c32744fae0c82210cc77168c88e14f590b414 Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Wed, 18 Jun 2025 10:08:02 +0000 Subject: [PATCH 1/3] feat: Preview can now show presets and validate them --- cli/clidisplay/resources.go | 29 ++++++++++++++++ cli/root.go | 27 ++++++++++++++- extract/preset.go | 31 +++++++++++++++++ preset.go | 53 +++++++++++++++++++++++++++++ preview.go | 67 +++++++++++++++++++++++++++++++++++++ preview_test.go | 4 +++ types/preset.go | 17 ++++++++++ 7 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 extract/preset.go create mode 100644 preset.go create mode 100644 types/preset.go diff --git a/cli/clidisplay/resources.go b/cli/clidisplay/resources.go index f7eab6e..063086a 100644 --- a/cli/clidisplay/resources.go +++ b/cli/clidisplay/resources.go @@ -74,6 +74,35 @@ func Parameters(writer io.Writer, params []types.Parameter, files map[string]*hc _, _ = fmt.Fprintln(writer, tableWriter.Render()) } +func Presets(writer io.Writer, presets []types.Preset, files map[string]*hcl.File) { + tableWriter := table.NewWriter() + tableWriter.SetStyle(table.StyleLight) + tableWriter.Style().Options.SeparateColumns = false + row := table.Row{"Preset"} + tableWriter.AppendHeader(row) + for _, p := range presets { + tableWriter.AppendRow(table.Row{ + fmt.Sprintf("%s\n%s", p.Name, formatPresetParameters(p.Parameters)), + }) + if hcl.Diagnostics(p.Diagnostics).HasErrors() { + var out bytes.Buffer + WriteDiagnostics(&out, files, hcl.Diagnostics(p.Diagnostics)) + tableWriter.AppendRow(table.Row{out.String()}) + } + + tableWriter.AppendSeparator() + } + _, _ = fmt.Fprintln(writer, tableWriter.Render()) +} + +func formatPresetParameters(presetParameters map[string]string) string { + var str strings.Builder + for presetParamName, PresetParamValue := range presetParameters { + _, _ = str.WriteString(fmt.Sprintf("%s = %s\n", presetParamName, PresetParamValue)) + } + return str.String() +} + func formatOptions(selected []string, options []*types.ParameterOption) string { var str strings.Builder sep := "" diff --git a/cli/root.go b/cli/root.go index ab1afc5..9b27aa0 100644 --- a/cli/root.go +++ b/cli/root.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "os" + "slices" "strings" "github.com/hashicorp/hcl/v2" @@ -27,6 +28,7 @@ func (r *RootCmd) Root() *serpent.Command { vars []string groups []string planJSON string + preset string ) cmd := &serpent.Command{ Use: "codertf", @@ -64,10 +66,25 @@ func (r *RootCmd) Root() *serpent.Command { Default: "", Value: serpent.StringArrayOf(&groups), }, + { + Name: "preset", + Description: "Name of the preset to define parameters. Run preview without this flag first to see a list of presets.", + Flag: "preset", + FlagShorthand: "s", + Default: "", + Value: serpent.StringOf(&preset), + }, }, Handler: func(i *serpent.Invocation) error { dfs := os.DirFS(dir) + ctx := i.Context() + + presets, _ := preview.PreviewPresets(ctx, dfs) + chosenPresetIndex := slices.IndexFunc(presets, func(p types.Preset) bool { + return p.Name == preset + }) + rvars := make(map[string]string) for _, val := range vars { parts := strings.Split(val, "=") @@ -76,6 +93,11 @@ func (r *RootCmd) Root() *serpent.Command { } rvars[parts[0]] = parts[1] } + if chosenPresetIndex != -1 { + for paramName, paramValue := range presets[chosenPresetIndex].Parameters { + rvars[paramName] = paramValue + } + } input := preview.Input{ PlanJSONPath: planJSON, @@ -85,7 +107,6 @@ func (r *RootCmd) Root() *serpent.Command { }, } - ctx := i.Context() output, diags := preview.Preview(ctx, input, dfs) if output == nil { return diags @@ -103,6 +124,10 @@ func (r *RootCmd) Root() *serpent.Command { clidisplay.WriteDiagnostics(os.Stderr, output.Files, diags) } + if chosenPresetIndex == -1 { + clidisplay.Presets(os.Stdout, presets, output.Files) + } + clidisplay.Parameters(os.Stdout, output.Parameters, output.Files) if !output.ModuleOutput.IsNull() && !(output.ModuleOutput.Type().IsObjectType() && output.ModuleOutput.LengthInt() == 0) { diff --git a/extract/preset.go b/extract/preset.go new file mode 100644 index 0000000..32eec58 --- /dev/null +++ b/extract/preset.go @@ -0,0 +1,31 @@ +package extract + +import ( + "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/coder/preview/types" + "github.com/hashicorp/hcl/v2" +) + +func PresetFromBlock(block *terraform.Block) (*types.Preset, hcl.Diagnostics) { + var diags hcl.Diagnostics + + pName, nameDiag := requiredString(block, "name") + if nameDiag != nil { + diags = append(diags, nameDiag) + } + + p := types.Preset{ + PresetData: types.PresetData{ + Name: pName, + Parameters: make(map[string]string), + }, + Diagnostics: types.Diagnostics{}, + } + + params := block.GetAttribute("parameters").AsMapValue() + for presetParamName, presetParamValue := range params.Value() { + p.Parameters[presetParamName] = presetParamValue + } + + return &p, diags +} diff --git a/preset.go b/preset.go new file mode 100644 index 0000000..8a21e3b --- /dev/null +++ b/preset.go @@ -0,0 +1,53 @@ +package preview + +import ( + "fmt" + "slices" + + "github.com/aquasecurity/trivy/pkg/iac/terraform" + "github.com/hashicorp/hcl/v2" + + "github.com/coder/preview/extract" + "github.com/coder/preview/types" +) + +func presets(modules terraform.Modules, parameters []types.Parameter) ([]types.Preset, hcl.Diagnostics) { + diags := make(hcl.Diagnostics, 0) + presets := make([]types.Preset, 0) + + for _, mod := range modules { + blocks := mod.GetDatasByType(types.BlockTypePreset) + for _, block := range blocks { + preset, pDiags := extract.PresetFromBlock(block) + if len(pDiags) > 0 { + diags = diags.Extend(pDiags) + } + + if preset == nil { + continue + } + + for paramName, paramValue := range preset.Parameters { + templateParamIndex := slices.IndexFunc(parameters, func(p types.Parameter) bool { + return p.Name == paramName + }) + if templateParamIndex == -1 { + preset.Diagnostics = append(preset.Diagnostics, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Undefined Parameter", + Detail: fmt.Sprintf("Preset %q requires parameter %q, but it is not defined by the template.", preset.Name, paramName), + }) + continue + } + templateParam := parameters[templateParamIndex] + for _, diag := range templateParam.Valid(types.StringLiteral(paramValue)) { + preset.Diagnostics = append(preset.Diagnostics, diag) + } + } + + presets = append(presets, *preset) + } + } + + return presets, diags +} diff --git a/preview.go b/preview.go index 62a94e6..49a052a 100644 --- a/preview.go +++ b/preview.go @@ -209,3 +209,70 @@ func tfVarFiles(path string, dir fs.FS) ([]string, error) { } return files, nil } + +func PreviewPresets(ctx context.Context, dir fs.FS) ([]types.Preset, hcl.Diagnostics) { + // The trivy package works with `github.com/zclconf/go-cty`. This package is + // similar to `reflect` in its usage. This package can panic if types are + // misused. To protect the caller, a general `recover` is used to catch any + // mistakes. If this happens, there is a developer bug that needs to be resolved. + var diagnostics hcl.Diagnostics + defer func() { + if r := recover(); r != nil { + diagnostics.Extend(hcl.Diagnostics{ + { + Severity: hcl.DiagError, + Summary: "Panic occurred in preview. This should not happen, please report this to Coder.", + Detail: fmt.Sprintf("panic in preview: %+v", r), + }, + }) + } + }() + + logger := slog.New(slog.DiscardHandler) + + varFiles, err := tfVarFiles("", dir) + if err != nil { + return nil, hcl.Diagnostics{ + { + Severity: hcl.DiagError, + Summary: "Files not found", + Detail: err.Error(), + }, + } + } + + // moduleSource is "" for a local module + p := parser.New(dir, "", + parser.OptionWithLogger(logger), + parser.OptionStopOnHCLError(false), + parser.OptionWithDownloads(false), + parser.OptionWithSkipCachedModules(true), + parser.OptionWithTFVarsPaths(varFiles...), + ) + + err = p.ParseFS(ctx, ".") + if err != nil { + return nil, hcl.Diagnostics{ + { + Severity: hcl.DiagError, + Summary: "Parse terraform files", + Detail: err.Error(), + }, + } + } + + modules, err := p.EvaluateAll(ctx) + if err != nil { + return nil, hcl.Diagnostics{ + { + Severity: hcl.DiagError, + Summary: "Evaluate terraform files", + Detail: err.Error(), + }, + } + } + + rp, rpDiags := parameters(modules) + presets, presetDiags := presets(modules, rp) + return presets, diagnostics.Extend(rpDiags).Extend(presetDiags) +} diff --git a/preview_test.go b/preview_test.go index 59a8761..8e1b7d9 100644 --- a/preview_test.go +++ b/preview_test.go @@ -42,6 +42,7 @@ func Test_Extract(t *testing.T) { expTags map[string]string unknownTags []string params map[string]assertParam + presets []types.Preset warnings []*regexp.Regexp }{ { @@ -543,6 +544,9 @@ func Test_Extract(t *testing.T) { require.True(t, ok, "unknown parameter %s", param.Name) check(t, param) } + + presets, diags := preview.PreviewPresets(context.Background(), dirFs) + assert.ElementsMatch(t, tc.presets, presets) }) } } diff --git a/types/preset.go b/types/preset.go new file mode 100644 index 0000000..65f5d0a --- /dev/null +++ b/types/preset.go @@ -0,0 +1,17 @@ +package types + +const ( + BlockTypePreset = "coder_workspace_preset" +) + +type Preset struct { + PresetData + // Diagnostics is used to store any errors that occur during parsing + // of the parameter. + Diagnostics Diagnostics `json:"diagnostics"` +} + +type PresetData struct { + Name string `json:"name"` + Parameters map[string]string `json:"parameters"` +} From 643c5ee93f55c58a34f828e71e789ef3ce8ea284 Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Sun, 13 Jul 2025 20:02:40 +0000 Subject: [PATCH 2/3] integrate preset validation into preview.Preview add tests --- extract/parameter.go | 15 +++--- extract/preset.go | 34 ++++++++++---- preset.go | 31 +++++++------ preview.go | 70 ++-------------------------- preview_test.go | 82 +++++++++++++++++++++++++++++++-- testdata/emptypreset/main.tf | 11 +++++ testdata/invalidpresets/main.tf | 67 +++++++++++++++++++++++++++ types/preset.go | 3 +- 8 files changed, 213 insertions(+), 100 deletions(-) create mode 100644 testdata/emptypreset/main.tf create mode 100644 testdata/invalidpresets/main.tf diff --git a/extract/parameter.go b/extract/parameter.go index 0d45a8b..d40a6cc 100644 --- a/extract/parameter.go +++ b/extract/parameter.go @@ -274,15 +274,18 @@ func requiredString(block *terraform.Block, key string) (string, *hcl.Diagnostic } diag := &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: fmt.Sprintf("Invalid %q attribute for block %s", key, block.Label()), - Detail: fmt.Sprintf("Expected a string, got %q", typeName), - Subject: &(tyAttr.HCLAttribute().Range), - //Context: &(block.HCLBlock().DefRange), - Expression: tyAttr.HCLAttribute().Expr, + Severity: hcl.DiagError, + Summary: fmt.Sprintf("Invalid %q attribute for block %s", key, block.Label()), + Detail: fmt.Sprintf("Expected a string, got %q", typeName), EvalContext: block.Context().Inner(), } + if tyAttr.IsNotNil() { + diag.Subject = &(tyAttr.HCLAttribute().Range) + // diag.Context = &(block.HCLBlock().DefRange) + diag.Expression = tyAttr.HCLAttribute().Expr + } + if !tyVal.IsWhollyKnown() { refs := hclext.ReferenceNames(tyAttr.HCLAttribute().Expr) if len(refs) > 0 { diff --git a/extract/preset.go b/extract/preset.go index 32eec58..01c9310 100644 --- a/extract/preset.go +++ b/extract/preset.go @@ -6,26 +6,40 @@ import ( "github.com/hashicorp/hcl/v2" ) -func PresetFromBlock(block *terraform.Block) (*types.Preset, hcl.Diagnostics) { - var diags hcl.Diagnostics - - pName, nameDiag := requiredString(block, "name") - if nameDiag != nil { - diags = append(diags, nameDiag) - } - +func PresetFromBlock(block *terraform.Block) types.Preset { p := types.Preset{ PresetData: types.PresetData{ - Name: pName, Parameters: make(map[string]string), }, Diagnostics: types.Diagnostics{}, } + if !block.IsResourceType(types.BlockTypePreset) { + p.Diagnostics = append(p.Diagnostics, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid Preset", + Detail: "Block is not a preset", + }) + return p + } + + pName, nameDiag := requiredString(block, "name") + if nameDiag != nil { + p.Diagnostics = append(p.Diagnostics, nameDiag) + } + p.Name = pName + + // GetAttribute and AsMapValue both gracefully handle `nil`, `null` and `unknown` values. + // All of these return an empty map, which then makes the loop below a no-op. params := block.GetAttribute("parameters").AsMapValue() for presetParamName, presetParamValue := range params.Value() { p.Parameters[presetParamName] = presetParamValue } - return &p, diags + defaultAttr := block.GetAttribute("default") + if defaultAttr != nil { + p.Default = defaultAttr.Value().True() + } + + return p } diff --git a/preset.go b/preset.go index 8a21e3b..0e5ca74 100644 --- a/preset.go +++ b/preset.go @@ -11,20 +11,25 @@ import ( "github.com/coder/preview/types" ) -func presets(modules terraform.Modules, parameters []types.Parameter) ([]types.Preset, hcl.Diagnostics) { - diags := make(hcl.Diagnostics, 0) - presets := make([]types.Preset, 0) +// presets extracts all presets from the given modules. It then validates the name, +// parameters and default preset. +func presets(modules terraform.Modules, parameters []types.Parameter) []types.Preset { + foundPresets := make([]types.Preset, 0) + var defaultPreset *types.Preset for _, mod := range modules { blocks := mod.GetDatasByType(types.BlockTypePreset) for _, block := range blocks { - preset, pDiags := extract.PresetFromBlock(block) - if len(pDiags) > 0 { - diags = diags.Extend(pDiags) - } - - if preset == nil { - continue + preset := extract.PresetFromBlock(block) + switch true { + case defaultPreset != nil && preset.Default: + preset.Diagnostics = append(preset.Diagnostics, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Multiple default presets", + Detail: fmt.Sprintf("Only one preset can be marked as default. %q is already marked as default", defaultPreset.Name), + }) + case defaultPreset == nil && preset.Default: + defaultPreset = &preset } for paramName, paramValue := range preset.Parameters { @@ -35,7 +40,7 @@ func presets(modules terraform.Modules, parameters []types.Parameter) ([]types.P preset.Diagnostics = append(preset.Diagnostics, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Undefined Parameter", - Detail: fmt.Sprintf("Preset %q requires parameter %q, but it is not defined by the template.", preset.Name, paramName), + Detail: fmt.Sprintf("Preset parameter %q is not defined by the template.", paramName), }) continue } @@ -45,9 +50,9 @@ func presets(modules terraform.Modules, parameters []types.Parameter) ([]types.P } } - presets = append(presets, *preset) + foundPresets = append(foundPresets, preset) } } - return presets, diags + return foundPresets } diff --git a/preview.go b/preview.go index 49a052a..8d041fa 100644 --- a/preview.go +++ b/preview.go @@ -38,6 +38,7 @@ type Output struct { Parameters []types.Parameter `json:"parameters"` WorkspaceTags types.TagBlocks `json:"workspace_tags"` + Presets []types.Preset `json:"presets"` // Files is included for printing diagnostics. // They can be marshalled, but not unmarshalled. This is a limitation // of the HCL library. @@ -162,6 +163,7 @@ func Preview(ctx context.Context, input Input, dir fs.FS) (output *Output, diagn diags := make(hcl.Diagnostics, 0) rp, rpDiags := parameters(modules) + presets := presets(modules, rp) tags, tagDiags := workspaceTags(modules, p.Files()) // Add warnings @@ -171,6 +173,7 @@ func Preview(ctx context.Context, input Input, dir fs.FS) (output *Output, diagn ModuleOutput: outputs, Parameters: rp, WorkspaceTags: tags, + Presets: presets, Files: p.Files(), }, diags.Extend(rpDiags).Extend(tagDiags) } @@ -209,70 +212,3 @@ func tfVarFiles(path string, dir fs.FS) ([]string, error) { } return files, nil } - -func PreviewPresets(ctx context.Context, dir fs.FS) ([]types.Preset, hcl.Diagnostics) { - // The trivy package works with `github.com/zclconf/go-cty`. This package is - // similar to `reflect` in its usage. This package can panic if types are - // misused. To protect the caller, a general `recover` is used to catch any - // mistakes. If this happens, there is a developer bug that needs to be resolved. - var diagnostics hcl.Diagnostics - defer func() { - if r := recover(); r != nil { - diagnostics.Extend(hcl.Diagnostics{ - { - Severity: hcl.DiagError, - Summary: "Panic occurred in preview. This should not happen, please report this to Coder.", - Detail: fmt.Sprintf("panic in preview: %+v", r), - }, - }) - } - }() - - logger := slog.New(slog.DiscardHandler) - - varFiles, err := tfVarFiles("", dir) - if err != nil { - return nil, hcl.Diagnostics{ - { - Severity: hcl.DiagError, - Summary: "Files not found", - Detail: err.Error(), - }, - } - } - - // moduleSource is "" for a local module - p := parser.New(dir, "", - parser.OptionWithLogger(logger), - parser.OptionStopOnHCLError(false), - parser.OptionWithDownloads(false), - parser.OptionWithSkipCachedModules(true), - parser.OptionWithTFVarsPaths(varFiles...), - ) - - err = p.ParseFS(ctx, ".") - if err != nil { - return nil, hcl.Diagnostics{ - { - Severity: hcl.DiagError, - Summary: "Parse terraform files", - Detail: err.Error(), - }, - } - } - - modules, err := p.EvaluateAll(ctx) - if err != nil { - return nil, hcl.Diagnostics{ - { - Severity: hcl.DiagError, - Summary: "Evaluate terraform files", - Detail: err.Error(), - }, - } - } - - rp, rpDiags := parameters(modules) - presets, presetDiags := presets(modules, rp) - return presets, diagnostics.Extend(rpDiags).Extend(presetDiags) -} diff --git a/preview_test.go b/preview_test.go index 8e1b7d9..341b8b2 100644 --- a/preview_test.go +++ b/preview_test.go @@ -42,7 +42,7 @@ func Test_Extract(t *testing.T) { expTags map[string]string unknownTags []string params map[string]assertParam - presets []types.Preset + presets func(t *testing.T, presets []types.Preset) warnings []*regexp.Regexp }{ { @@ -243,6 +243,80 @@ func Test_Extract(t *testing.T) { errorDiagnostics("Required"), }, }, + { + name: "empty preset", + dir: "emptypreset", + expTags: map[string]string{}, + input: preview.Input{}, + unknownTags: []string{}, + presets: func(t *testing.T, presets []types.Preset) { + require.Len(t, presets, 1) + preset := presets[0] + require.Len(t, preset.Diagnostics, 1) + require.Equal(t, preset.Diagnostics[0].Summary, "Invalid \"name\" attribute for block coder_workspace_preset.test") + require.Equal(t, preset.Diagnostics[0].Detail, "Expected a string, got \"\"") + }, + failPreview: false, + }, + { + name: "invalid presets", + dir: "invalidpresets", + expTags: map[string]string{}, + input: preview.Input{}, + unknownTags: []string{}, + params: map[string]assertParam{ + "valid_parameter_name": ap(). + optVals("valid_option_value"), + }, + presets: func(t *testing.T, presets []types.Preset) { + presetMap := map[string]func(t *testing.T, preset types.Preset){ + "": func(t *testing.T, preset types.Preset) { + require.Len(t, preset.Diagnostics, 0) + }, + "empty_parameters": func(t *testing.T, preset types.Preset) { + require.Len(t, preset.Diagnostics, 0) + }, + "no_parameters": func(t *testing.T, preset types.Preset) { + require.Len(t, preset.Diagnostics, 0) + }, + "invalid_parameter_name": func(t *testing.T, preset types.Preset) { + require.Len(t, preset.Diagnostics, 1) + require.Equal(t, preset.Diagnostics[0].Summary, "Undefined Parameter") + require.Equal(t, preset.Diagnostics[0].Detail, "Preset parameter \"invalid_parameter_name\" is not defined by the template.") + }, + "invalid_parameter_value": func(t *testing.T, preset types.Preset) { + require.Len(t, preset.Diagnostics, 1) + require.Equal(t, preset.Diagnostics[0].Summary, "Value must be a valid option") + require.Equal(t, preset.Diagnostics[0].Detail, "the value \"invalid_value\" must be defined as one of options") + }, + "valid_preset": func(t *testing.T, preset types.Preset) { + require.Len(t, preset.Diagnostics, 0) + require.Equal(t, preset.Parameters, map[string]string{ + "valid_parameter_name": "valid_option_value", + }) + }, + } + + for _, preset := range presets { + if fn, ok := presetMap[preset.Name]; ok { + fn(t, preset) + } + } + + var defaultPresetsWithError int + for _, preset := range presets { + if preset.Name == "default_preset" || preset.Name == "another_default_preset" { + for _, diag := range preset.Diagnostics { + if diag.Summary == "Multiple default presets" { + defaultPresetsWithError++ + break + } + } + } + } + require.Equal(t, 1, defaultPresetsWithError, "exactly one default preset should have the multiple defaults error") + }, + }, { name: "required", dir: "required", @@ -545,8 +619,10 @@ func Test_Extract(t *testing.T) { check(t, param) } - presets, diags := preview.PreviewPresets(context.Background(), dirFs) - assert.ElementsMatch(t, tc.presets, presets) + // Assert presets + if tc.presets != nil { + tc.presets(t, output.Presets) + } }) } } diff --git a/testdata/emptypreset/main.tf b/testdata/emptypreset/main.tf new file mode 100644 index 0000000..91fa661 --- /dev/null +++ b/testdata/emptypreset/main.tf @@ -0,0 +1,11 @@ +terraform { + required_providers { + coder = { + source = "coder/coder" + version = "2.8.0" + } + } +} + +data "coder_workspace_preset" "test" { +} \ No newline at end of file diff --git a/testdata/invalidpresets/main.tf b/testdata/invalidpresets/main.tf new file mode 100644 index 0000000..180157f --- /dev/null +++ b/testdata/invalidpresets/main.tf @@ -0,0 +1,67 @@ +terraform { + required_providers { + coder = { + source = "coder/coder" + version = "2.8.0" + } + } +} + +data "coder_parameter" "valid_parameter" { + name = "valid_parameter_name" + default = "valid_option_value" + option { + name = "valid_option_name" + value = "valid_option_value" + } +} + +data "coder_workspace_preset" "invalid_preset_name" { + name = "" +} + +data "coder_workspace_preset" "no_parameters" { + name = "no_parameters" +} + +data "coder_workspace_preset" "empty_parameters" { + name = "empty_parameters" + parameters = {} +} + +data "coder_workspace_preset" "invalid_parameter_name" { + name = "invalid_parameter_name" + parameters = { + "invalid_parameter_name" = "irrelevant_value" + } +} + +data "coder_workspace_preset" "invalid_parameter_value" { + name = "invalid_parameter_value" + parameters = { + "valid_parameter_name" = "invalid_value" + } +} + +data "coder_workspace_preset" "valid_preset" { + name = "valid_preset" + parameters = { + "valid_parameter_name" = "valid_option_value" + } +} + +data "coder_workspace_preset" "default_preset" { + name = "default_preset" + parameters = { + "valid_parameter_name" = "valid_option_value" + } + default = true +} + +data "coder_workspace_preset" "another_default_preset" { + name = "another_default_preset" + parameters = { + "valid_parameter_name" = "valid_option_value" + } + default = true +} \ No newline at end of file diff --git a/types/preset.go b/types/preset.go index 65f5d0a..da05e41 100644 --- a/types/preset.go +++ b/types/preset.go @@ -7,11 +7,12 @@ const ( type Preset struct { PresetData // Diagnostics is used to store any errors that occur during parsing - // of the parameter. + // of the preset. Diagnostics Diagnostics `json:"diagnostics"` } type PresetData struct { Name string `json:"name"` Parameters map[string]string `json:"parameters"` + Default bool `json:"default"` } From 17616ecf763ab36683666814dd4dc784743b323a Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Sun, 13 Jul 2025 20:09:48 +0000 Subject: [PATCH 3/3] Remove defunct tests --- cli/root.go | 3 ++- preview_test.go | 18 ------------------ testdata/emptypreset/main.tf | 11 ----------- testdata/invalidpresets/main.tf | 4 ---- 4 files changed, 2 insertions(+), 34 deletions(-) delete mode 100644 testdata/emptypreset/main.tf diff --git a/cli/root.go b/cli/root.go index 9b27aa0..0971018 100644 --- a/cli/root.go +++ b/cli/root.go @@ -80,7 +80,8 @@ func (r *RootCmd) Root() *serpent.Command { ctx := i.Context() - presets, _ := preview.PreviewPresets(ctx, dfs) + output, _ := preview.Preview(ctx, preview.Input{}, dfs) + presets := output.Presets chosenPresetIndex := slices.IndexFunc(presets, func(p types.Preset) bool { return p.Name == preset }) diff --git a/preview_test.go b/preview_test.go index 341b8b2..177109b 100644 --- a/preview_test.go +++ b/preview_test.go @@ -243,21 +243,6 @@ func Test_Extract(t *testing.T) { errorDiagnostics("Required"), }, }, - { - name: "empty preset", - dir: "emptypreset", - expTags: map[string]string{}, - input: preview.Input{}, - unknownTags: []string{}, - presets: func(t *testing.T, presets []types.Preset) { - require.Len(t, presets, 1) - preset := presets[0] - require.Len(t, preset.Diagnostics, 1) - require.Equal(t, preset.Diagnostics[0].Summary, "Invalid \"name\" attribute for block coder_workspace_preset.test") - require.Equal(t, preset.Diagnostics[0].Detail, "Expected a string, got \"\"") - }, - failPreview: false, - }, { name: "invalid presets", dir: "invalidpresets", @@ -270,9 +255,6 @@ func Test_Extract(t *testing.T) { }, presets: func(t *testing.T, presets []types.Preset) { presetMap := map[string]func(t *testing.T, preset types.Preset){ - "": func(t *testing.T, preset types.Preset) { - require.Len(t, preset.Diagnostics, 0) - }, "empty_parameters": func(t *testing.T, preset types.Preset) { require.Len(t, preset.Diagnostics, 0) }, diff --git a/testdata/emptypreset/main.tf b/testdata/emptypreset/main.tf deleted file mode 100644 index 91fa661..0000000 --- a/testdata/emptypreset/main.tf +++ /dev/null @@ -1,11 +0,0 @@ -terraform { - required_providers { - coder = { - source = "coder/coder" - version = "2.8.0" - } - } -} - -data "coder_workspace_preset" "test" { -} \ No newline at end of file diff --git a/testdata/invalidpresets/main.tf b/testdata/invalidpresets/main.tf index 180157f..d9abd56 100644 --- a/testdata/invalidpresets/main.tf +++ b/testdata/invalidpresets/main.tf @@ -16,10 +16,6 @@ data "coder_parameter" "valid_parameter" { } } -data "coder_workspace_preset" "invalid_preset_name" { - name = "" -} - data "coder_workspace_preset" "no_parameters" { name = "no_parameters" }