-
Notifications
You must be signed in to change notification settings - Fork 1
feat: Preview can now show presets and validate them #149
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
Comment on lines
+14
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the function name |
||
|
||
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)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice 👍 |
||
preset.Diagnostics = append(preset.Diagnostics, diag) | ||
} | ||
} | ||
|
||
presets = append(presets, *preset) | ||
} | ||
} | ||
|
||
return presets, diags | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -208,3 +208,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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ideally we'd just put this in But I do see it can throw top level diags. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had it in |
||
// 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) | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -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. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
Diagnostics Diagnostics `json:"diagnostics"` | ||||||
} | ||||||
|
||||||
type PresetData struct { | ||||||
Name string `json:"name"` | ||||||
Parameters map[string]string `json:"parameters"` | ||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder what happens on
unknown
ornull
values 🤔