Skip to content

Commit 5e8f1c5

Browse files
committed
handle importing arbitrary resource data
1 parent f02a46f commit 5e8f1c5

File tree

6 files changed

+90
-16
lines changed

6 files changed

+90
-16
lines changed

attr.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,18 @@ func (a *expectedAttribute) string() string {
7777
}
7878

7979
func (a *expectedAttribute) expectedTypeError(attr *terraform.Attribute, expectedType string) {
80+
var fn string
81+
if attr.IsNil() || attr.Type().Equals(cty.NilType) {
82+
fn = "nil"
83+
} else {
84+
fn = attr.Type().FriendlyName()
85+
}
86+
8087
a.error(hcl.Diagnostics{
8188
{
8289
Severity: hcl.DiagError,
8390
Summary: "Invalid attribute type",
84-
Detail: fmt.Sprintf("The attribute %q must be of type %q, found type %q", attr.Name(), expectedType, attr.Type().FriendlyName()),
91+
Detail: fmt.Sprintf("The attribute %q must be of type %q, found type %q", attr.Name(), expectedType, fn),
8592
Subject: &attr.HCLAttribute().Range,
8693
Context: &a.p.block.HCLBlock().DefRange,
8794
Expression: attr.HCLAttribute().Expr,

cli/clidisplay/resources.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import (
55
"io"
66
"strings"
77

8-
"github.com/coder/preview/types"
98
"github.com/hashicorp/hcl/v2"
109
"github.com/jedib0t/go-pretty/v6/table"
10+
"github.com/zclconf/go-cty/cty"
11+
12+
"github.com/coder/preview/types"
1113
)
1214

1315
func WorkspaceTags(writer io.Writer, tags types.TagBlocks) hcl.Diagnostics {
@@ -63,12 +65,14 @@ func Parameters(writer io.Writer, params []types.Parameter) {
6365
strVal = "null"
6466
} else if !p.Value.Value.IsKnown() {
6567
strVal = "unknown"
66-
} else {
68+
} else if value.Type().Equals(cty.String) {
6769
strVal = value.AsString()
70+
} else {
71+
strVal = value.GoString()
6872
}
6973

7074
tableWriter.AppendRow(table.Row{
71-
fmt.Sprintf("%s: %s\n%s", p.Name, p.Description, formatOptions(strVal, p.Options)),
75+
fmt.Sprintf("%s (%s): %s\n%s", p.Name, p.BlockName, p.Description, formatOptions(strVal, p.Options)),
7276
})
7377
tableWriter.AppendSeparator()
7478
}
@@ -79,6 +83,7 @@ func formatOptions(selected string, options []*types.RichParameterOption) string
7983
var str strings.Builder
8084
sep := ""
8185
found := false
86+
8287
for _, opt := range options {
8388
str.WriteString(sep)
8489
prefix := "[ ]"

parameter.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,29 @@ func RichParameters(modules terraform.Modules) ([]types.Parameter, hcl.Diagnosti
3232

3333
// Find the value of the parameter from the context.
3434
paramValue := richParameterValue(block)
35+
var defVal string
36+
37+
// default can be nil if the references are not resolved.
38+
def := block.GetAttribute("default").Value()
39+
if def.Equals(cty.NilVal).True() {
40+
defVal = "<nil>"
41+
} else if !def.IsKnown() {
42+
defVal = "<unknown>"
43+
} else {
44+
defVal = p.attr("default").string()
45+
}
3546

3647
param := types.Parameter{
3748
Value: types.ParameterValue{
3849
Value: paramValue,
3950
},
4051
RichParameter: types.RichParameter{
52+
BlockName: block.Labels()[1],
4153
Name: p.attr("name").required().string(),
4254
Description: p.attr("description").string(),
4355
Type: "",
4456
Mutable: false,
45-
DefaultValue: p.attr("default").string(),
57+
DefaultValue: defVal,
4658
Icon: p.attr("icon").string(),
4759
Options: paramOptions,
4860
Validation: nil,

plan.go

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"io"
77
"io/fs"
88
"log"
9+
"reflect"
910
"strings"
1011

1112
"github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/tfjson/parser"
@@ -45,11 +46,11 @@ func PlanJSONHook(dfs fs.FS, input Input) (func(ctx *tfcontext.Context, blocks t
4546
continue
4647
}
4748

48-
if parts[0] == "data" && !strings.Contains(resource.Type, "coder") {
49+
if parts[0] != "data" || strings.Contains(parts[1], "coder") {
4950
continue
5051
}
5152

52-
val, err := attributeCtyVal(resource.AttributeValues)
53+
val, err := toCtyValue(resource.AttributeValues)
5354
if err != nil {
5455
// TODO: Remove log
5556
log.Printf("unable to determine value of resource %q: %v", resource.Address, err)
@@ -62,21 +63,49 @@ func PlanJSONHook(dfs fs.FS, input Input) (func(ctx *tfcontext.Context, blocks t
6263
}, nil
6364
}
6465

65-
func attributeCtyVal(attr map[string]interface{}) (cty.Value, error) {
66-
mv := make(map[string]cty.Value)
67-
for k, v := range attr {
68-
ty, err := gocty.ImpliedType(v)
66+
func toCtyValue(a any) (cty.Value, error) {
67+
if a == nil {
68+
return cty.NilVal, nil
69+
}
70+
av := reflect.ValueOf(a)
71+
switch av.Type().Kind() {
72+
case reflect.Slice, reflect.Array:
73+
sv := make([]cty.Value, 0, av.Len())
74+
for i := 0; i < av.Len(); i++ {
75+
v, err := toCtyValue(av.Index(i).Interface())
76+
if err != nil {
77+
return cty.NilVal, fmt.Errorf("slice value %d: %w", i, err)
78+
}
79+
sv = append(sv, v)
80+
}
81+
return cty.ListVal(sv), nil
82+
case reflect.Map:
83+
if av.Type().Key().Kind() != reflect.String {
84+
return cty.NilVal, fmt.Errorf("map keys must be string, found %q", av.Type().Key().Kind())
85+
}
86+
87+
mv := make(map[string]cty.Value)
88+
var err error
89+
for _, k := range av.MapKeys() {
90+
v := av.MapIndex(k)
91+
mv[k.String()], err = toCtyValue(v.Interface())
92+
if err != nil {
93+
return cty.NilVal, fmt.Errorf("map value %q: %w", k.String(), err)
94+
}
95+
}
96+
return cty.ObjectVal(mv), nil
97+
default:
98+
ty, err := gocty.ImpliedType(a)
6999
if err != nil {
70-
return cty.NilVal, fmt.Errorf("implied type for %q: %w", k, err)
100+
return cty.NilVal, fmt.Errorf("implied type: %w", err)
71101
}
72102

73-
mv[k], err = gocty.ToCtyValue(v, ty)
103+
cv, err := gocty.ToCtyValue(a, ty)
74104
if err != nil {
75-
return cty.NilVal, fmt.Errorf("implied value for %q: %w", k, err)
105+
return cty.NilVal, fmt.Errorf("implied value: %w", err)
76106
}
107+
return cv, nil
77108
}
78-
79-
return cty.ObjectVal(mv), nil
80109
}
81110

82111
// ParsePlanJSON can parse the JSON output of a Terraform plan.

preview_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,18 @@ import (
1010
"github.com/stretchr/testify/assert"
1111
"github.com/stretchr/testify/require"
1212
"github.com/zclconf/go-cty/cty"
13+
"github.com/zclconf/go-cty/cty/gocty"
1314

1415
"github.com/coder/preview"
1516
"github.com/coder/preview/types"
1617
)
1718

19+
func TestFoo(t *testing.T) {
20+
ty, err := gocty.ImpliedType([]any{1, 2, 3})
21+
require.NoError(t, err)
22+
fmt.Println(ty.FriendlyName())
23+
}
24+
1825
func Test_Extract(t *testing.T) {
1926
t.Parallel()
2027

@@ -145,6 +152,17 @@ func Test_Extract(t *testing.T) {
145152
},
146153
params: map[string]func(t *testing.T, parameter types.Parameter){},
147154
},
155+
{
156+
name: "aws instance list",
157+
dir: "instancelist",
158+
expTags: map[string]string{},
159+
input: preview.Input{
160+
PlanJSONPath: "before.json",
161+
ParameterValues: map[string]types.ParameterValue{},
162+
},
163+
expUnknowns: []string{},
164+
params: map[string]func(t *testing.T, parameter types.Parameter){},
165+
},
148166
} {
149167
t.Run(tc.name, func(t *testing.T) {
150168
t.Parallel()

types/parameter.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ type RichParameter struct {
3131
DisplayName string `json:"display_name"`
3232
Order int32 `json:"order"`
3333
Ephemeral bool `json:"ephemeral"`
34+
35+
// HCL props
36+
BlockName string `json:"block_name"`
3437
}
3538

3639
type ParameterValidation struct {

0 commit comments

Comments
 (0)