Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
57 changes: 57 additions & 0 deletions attrs/values.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package attrs

import (
"github.com/aquasecurity/trivy/pkg/iac/terraform"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/gocty"
)

type Values interface {
// Attr returns the value of the attribute with the given key.
// If the attribute does not exist, it returns cty.NilVal.
Attr(key string) cty.Value
}

func NewValues[T *terraform.Block | map[string]any](block T) Values {
switch v := any(block).(type) {
case *terraform.Block:
return &blockValues{Values: v}
case map[string]any:
return &mapValues{Values: v}
default:
panic("unsupported type")
}
}

type blockValues struct {
Values *terraform.Block
}

func (b *blockValues) Attr(key string) cty.Value {
attr := b.Values.GetAttribute(key)
if attr.IsNil() {
return cty.NilVal
}
return attr.Value()
}

type mapValues struct {
Values map[string]any
}

func (m *mapValues) Attr(key string) cty.Value {
val, ok := m.Values[key]
if !ok {
return cty.NilVal
}

gt, err := gocty.ImpliedType(val)
if err != nil {
return cty.NilVal
}
v, err := gocty.ToCtyValue(val, gt)
if err != nil {
return cty.NilVal
}
return v
}
2 changes: 1 addition & 1 deletion cli/clidisplay/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func Parameters(writer io.Writer, params []types.Parameter) {
_, _ = fmt.Fprintln(writer, tableWriter.Render())
}

func formatOptions(selected string, options []*types.RichParameterOption) string {
func formatOptions(selected string, options []*types.ParameterOption) string {
var str strings.Builder
sep := ""
found := false
Expand Down
64 changes: 64 additions & 0 deletions extract/json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package extract

import (
"fmt"
)

type stateParse struct {
errors []error
values map[string]any
}

func newStateParse(v map[string]any) *stateParse {
return &stateParse{
errors: make([]error, 0),
values: v,
}
}

func (p *stateParse) optionalString(key string) string {
return optional[string](p.values, key)
}

func (p *stateParse) optionalInteger(key string) int64 {
return optional[int64](p.values, key)
}

func (p *stateParse) optionalBool(key string) bool {
return optional[bool](p.values, key)
}

func (p *stateParse) nullableInteger(key string) *int64 {
if p.values[key] == nil {
return nil
}
v := optional[int64](p.values, key)
return &v
}

func (p *stateParse) string(key string) string {
v, err := expected[string](p.values, key)
if err != nil {
p.errors = append(p.errors, err)
return ""
}
return v
}

func optional[T any](vals map[string]any, key string) T {
v, _ := expected[T](vals, key)
return v
}

func expected[T any](vals map[string]any, key string) (T, error) {
v, ok := vals[key]
if !ok {
return *new(T), fmt.Errorf("missing required key %q", key)
}

val, ok := v.(T)
if !ok {
return *new(T), fmt.Errorf("key %q is not of type %T", key, v)
}
return val, nil
}
266 changes: 266 additions & 0 deletions extract/parameter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
package extract

import (
"fmt"
"strings"

"github.com/aquasecurity/trivy/pkg/iac/terraform"
"github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty"

"github.com/coder/preview/hclext"
"github.com/coder/preview/types"
)

func ParameterFromBlock(block *terraform.Block) (types.Parameter, hcl.Diagnostics) {
diags := required(block, "name", "type")
if diags.HasErrors() {
return types.Parameter{}, diags
}

pType, typDiag := requiredString("type", block)
if typDiag != nil {
diags = diags.Append(typDiag)
}

pName, nameDiag := requiredString("name", block)
if nameDiag != nil {
diags = diags.Append(nameDiag)
}

pVal, valDiags := richParameterValue(block)
diags = diags.Extend(valDiags)

if diags.HasErrors() {
return types.Parameter{}, diags
}

p := types.Parameter{
Value: types.ParameterValue{
Value: pVal,
},
RichParameter: types.RichParameter{
Name: pName,
Description: optionalString(block, "description"),
Type: pType,
Mutable: optionalBoolean(block, "mutable"),
// Default value is always written as a string, then converted
// to the correct type.
DefaultValue: optionalString(block, "default"),
Icon: optionalString(block, "icon"),
Options: make([]*types.ParameterOption, 0),
Validations: make([]*types.ParameterValidation, 0),
Required: optionalBoolean(block, "required"),
DisplayName: optionalString(block, "display_name"),
Order: optionalInteger(block, "order"),
Ephemeral: optionalBoolean(block, "ephemeral"),
BlockName: block.NameLabel(),
},
}

for _, b := range block.GetBlocks("option") {
opt, optDiags := ParameterOptionFromBlock(b)
diags = diags.Extend(optDiags)

if optDiags.HasErrors() {
continue
}

p.Options = append(p.Options, &opt)
}

for _, b := range block.GetBlocks("validation") {
valid, validDiags := ParameterValidationFromBlock(b)
diags = diags.Extend(validDiags)

if validDiags.HasErrors() {
continue
}

p.Validations = append(p.Validations, &valid)
}

return p, diags
}

func ParameterValidationFromBlock(block *terraform.Block) (types.ParameterValidation, hcl.Diagnostics) {
diags := required(block, "error")
if diags.HasErrors() {
return types.ParameterValidation{}, diags
}

pErr, errDiag := requiredString("error", block)
if errDiag != nil {
diags = diags.Append(errDiag)
}

if diags.HasErrors() {
return types.ParameterValidation{}, diags
}

p := types.ParameterValidation{
Regex: optionalString(block, "regex"),
Error: pErr,
Min: nullableInteger(block, "min"),
Max: nullableInteger(block, "max"),
Monotonic: optionalString(block, "monotonic"),
}

return p, diags
}

func ParameterOptionFromBlock(block *terraform.Block) (types.ParameterOption, hcl.Diagnostics) {
diags := required(block, "name", "value")
if diags.HasErrors() {
return types.ParameterOption{}, diags
}

pName, nameDiag := requiredString("name", block)
if nameDiag != nil {
diags = diags.Append(nameDiag)
}

pVal, valDiag := requiredString("value", block)
if valDiag != nil {
diags = diags.Append(valDiag)
}

if diags.HasErrors() {
return types.ParameterOption{}, diags
}

p := types.ParameterOption{
Name: pName,
Description: optionalString(block, "description"),
Value: pVal,
Icon: optionalString(block, "icon"),
}

return p, diags
}

func requiredString(key string, block *terraform.Block) (string, *hcl.Diagnostic) {
tyAttr := block.GetAttribute(key)
tyVal := tyAttr.Value()
if tyVal.Type() != cty.String {
diag := &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Invalid %q attribute", key),
Detail: fmt.Sprintf("Expected a string, got %q", tyVal.Type().FriendlyName()),
Subject: &(tyAttr.HCLAttribute().Range),
//Context: &(block.HCLBlock().DefRange),
Expression: tyAttr.HCLAttribute().Expr,
EvalContext: block.Context().Inner(),
}

if !tyVal.IsWhollyKnown() {
refs := hclext.ReferenceNames(tyAttr.HCLAttribute().Expr)
if len(refs) > 0 {
diag.Detail = fmt.Sprintf("Value is not known, check the references [%s] are resolvable",
strings.Join(refs, ", "))
}
}

return "", diag
}

return tyVal.AsString(), nil
}

func optionalBoolean(block *terraform.Block, key string) bool {
attr := block.GetAttribute(key)
if attr == nil || attr.IsNil() {
return false
}
val := attr.Value()
if val.Type() != cty.Bool {
return false
}

return val.True()
}

func nullableInteger(block *terraform.Block, key string) *int64 {
attr := block.GetAttribute(key)
if attr == nil || attr.IsNil() {
return nil
}
val := attr.Value()
if val.Type() != cty.Number {
return nil
}

i, acc := val.AsBigFloat().Int64()
var _ = acc // acc should be 0

return &i
}

func optionalInteger(block *terraform.Block, key string) int64 {
attr := block.GetAttribute(key)
if attr == nil || attr.IsNil() {
return 0
}
val := attr.Value()
if val.Type() != cty.Number {
return 0
}

i, acc := val.AsBigFloat().Int64()
var _ = acc // acc should be 0

return i
}

func optionalString(block *terraform.Block, key string) string {
attr := block.GetAttribute(key)
if attr == nil || attr.IsNil() {
return ""
}
val := attr.Value()
if val.Type() != cty.String {
return ""
}

return val.AsString()
}

func required(block *terraform.Block, keys ...string) hcl.Diagnostics {
var diags hcl.Diagnostics
for _, key := range keys {
attr := block.GetAttribute(key)
if attr == nil || attr.IsNil() || attr.Value() == cty.NilVal {
r := block.HCLBlock().Body.MissingItemRange()
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Missing required attribute %q", key),
Detail: fmt.Sprintf("The %s attribute is required", key),
Subject: &r,
Extra: nil,
})
}
}
return diags
}

func richParameterValue(block *terraform.Block) (cty.Value, hcl.Diagnostics) {
// Find the value of the parameter from the context.
paramPath := append([]string{"data"}, block.Labels()...)
valueRef := hclext.ScopeTraversalExpr(append(paramPath, "value")...)
return valueRef.Value(block.Context().Inner())
}

func ParameterCtyType(typ string) (cty.Type, error) {
switch typ {
case "string":
return cty.String, nil
case "number":
return cty.Number, nil
case "bool":
return cty.Bool, nil
case "list(string)":
return cty.List(cty.String), nil
default:
return cty.Type{}, fmt.Errorf("unsupported type: %q", typ)
}
}
Loading
Loading