Skip to content

Commit a13d060

Browse files
committedFeb 14, 2025··
basic submodule support in plan files
1 parent cef0cc0 commit a13d060

File tree

11 files changed

+1020
-50
lines changed

11 files changed

+1020
-50
lines changed
 

Diff for: ‎attr.go

+13
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,19 @@ func (a *expectedAttribute) required() *expectedAttribute {
6262
return a
6363
}
6464

65+
func (a *expectedAttribute) tryString() string {
66+
attr := a.p.block.GetAttribute(a.Key)
67+
if attr.IsNil() {
68+
return ""
69+
}
70+
71+
if attr.Type() != cty.String {
72+
return ""
73+
}
74+
75+
return attr.Value().AsString()
76+
}
77+
6578
func (a *expectedAttribute) string() string {
6679
attr := a.p.block.GetAttribute(a.Key)
6780
if attr.IsNil() {

Diff for: ‎go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -108,4 +108,4 @@ require (
108108
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
109109
)
110110

111-
replace github.com/aquasecurity/trivy => github.com/Emyrk/trivy v0.0.0-20250211144405-6377059f7705
111+
replace github.com/aquasecurity/trivy => github.com/Emyrk/trivy v0.0.0-20250214143920-a49c737d9481

Diff for: ‎go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -625,8 +625,8 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25
625625
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
626626
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
627627
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
628-
github.com/Emyrk/trivy v0.0.0-20250211144405-6377059f7705 h1:+xVBMqeiopGTM+MbCydHG2K1IJm/Vf0LJYuIm8Rky18=
629-
github.com/Emyrk/trivy v0.0.0-20250211144405-6377059f7705/go.mod h1:h/k++UOEKWxKbATF5462j0OpzrnYwkqHI0HAcLB5uuY=
628+
github.com/Emyrk/trivy v0.0.0-20250214143920-a49c737d9481 h1:7M01eVPCnJfQN2pUJ/CqUYf809i9VicrI+17ypeu+ps=
629+
github.com/Emyrk/trivy v0.0.0-20250214143920-a49c737d9481/go.mod h1:h/k++UOEKWxKbATF5462j0OpzrnYwkqHI0HAcLB5uuY=
630630
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw=
631631
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM=
632632
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 h1:UQ0AhxogsIRZDkElkblfnwjc3IaltCm2HUMvezQaL7s=

Diff for: ‎parameter.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func paramOption(block *terraform.Block) (*types.RichParameterOption, hcl.Diagno
8181
Name: p.attr("name").required().string(),
8282
Description: p.attr("description").string(),
8383
// Does it need to be a string?
84-
Value: p.attr("value").required().string(),
84+
Value: p.attr("value").required().tryString(),
8585
Icon: p.attr("icon").string(),
8686
}
8787
if p.diags.HasErrors() {

Diff for: ‎plan.go

+30-44
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import (
55
"fmt"
66
"io"
77
"io/fs"
8-
"log"
98
"reflect"
9+
"slices"
1010
"strings"
1111

1212
"github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/tfjson/parser"
@@ -31,67 +31,53 @@ func PlanJSONHook(dfs fs.FS, input Input) (func(ctx *tfcontext.Context, blocks t
3131
return nil, fmt.Errorf("unable to parse plan JSON: %w", err)
3232
}
3333

34-
//plan.PriorState
35-
3634
var _ = plan
3735
return func(ctx *tfcontext.Context, blocks terraform.Blocks, inputVars map[string]cty.Value) {
36+
// Do not recurse to child blocks.
37+
// TODO: Only load into the single parent context for the module.
3838
for _, block := range blocks {
39-
if block.InModule() {
40-
41-
x := block.ModuleKey()
42-
y := block.ModuleBlock().FullName()
43-
var _, _ = x, y
44-
fmt.Println(block.ModuleKey())
39+
planMod := priorPlanModule(plan, block)
40+
if planMod == nil {
4541
continue
4642
}
47-
48-
err = loadResourcesToContext(block.Context().Parent(), plan.PriorState.Values.RootModule.Resources)
43+
err = loadResourcesToContext(block.Context().Parent(), planMod.Resources)
4944
if err != nil {
45+
// TODO: Somehow handle this error
5046
panic(fmt.Sprintf("unable to load resources to context: %v", err))
5147
}
5248
}
53-
54-
// 'data' blocks are loaded into prior state
55-
//plan.PriorState.Values.RootModule.Resources
56-
for _, resource := range plan.PriorState.Values.RootModule.Resources {
57-
// TODO: Do index references exist here too?
58-
// TODO: Handle submodule nested resources
59-
60-
parts := strings.Split(resource.Address, ".")
61-
if len(parts) < 2 {
62-
continue
63-
}
64-
65-
if parts[0] != "data" || strings.Contains(parts[1], "coder") {
66-
continue
67-
}
68-
69-
val, err := toCtyValue(resource.AttributeValues)
70-
if err != nil {
71-
// TODO: Remove log
72-
log.Printf("unable to determine value of resource %q: %v", resource.Address, err)
73-
continue
74-
}
75-
76-
ctx.Set(val, parts...)
77-
}
78-
7949
}, nil
8050
}
8151

82-
func planResources(plan *tfjson.Plan, block *terraform.Block) error {
52+
func priorPlanModule(plan *tfjson.Plan, block *terraform.Block) *tfjson.StateModule {
8353
if !block.InModule() {
84-
return loadResourcesToContext(block.Context().Parent(), plan.PriorState.Values.RootModule.Resources)
54+
return plan.PriorState.Values.RootModule
8555
}
8656

87-
var path []string
57+
var modPath []string
8858
mod := block.ModuleBlock()
89-
9059
for {
91-
path = append([]string{mod.FullName()}, path...)
92-
break
60+
modPath = append([]string{mod.LocalName()}, modPath...)
61+
mod = mod.ModuleBlock()
62+
if mod == nil {
63+
break
64+
}
9365
}
94-
return nil
66+
67+
current := plan.PriorState.Values.RootModule
68+
for i := range modPath {
69+
idx := slices.IndexFunc(current.ChildModules, func(m *tfjson.StateModule) bool {
70+
return m.Address == strings.Join(modPath[:i+1], ".")
71+
})
72+
if idx == -1 {
73+
// Maybe throw a diag here?
74+
return nil
75+
}
76+
77+
current = current.ChildModules[idx]
78+
}
79+
80+
return current
9581
}
9682

9783
func loadResourcesToContext(ctx *tfcontext.Context, resources []*tfjson.StateResource) error {

Diff for: ‎preview_test.go

+14-1
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,17 @@ func Test_Extract(t *testing.T) {
179179
expTags: map[string]string{},
180180
input: preview.Input{
181181
ParameterValues: map[string]types.ParameterValue{},
182-
PlanJSONPath: "before.json",
182+
PlanJSONPath: "out.json",
183+
},
184+
expUnknowns: []string{},
185+
params: map[string]func(t *testing.T, parameter types.Parameter){},
186+
},
187+
{
188+
name: "test",
189+
dir: "test",
190+
expTags: map[string]string{},
191+
input: preview.Input{
192+
ParameterValues: map[string]types.ParameterValue{},
183193
},
184194
expUnknowns: []string{},
185195
params: map[string]func(t *testing.T, parameter types.Parameter){},
@@ -203,6 +213,9 @@ func Test_Extract(t *testing.T) {
203213
require.True(t, diags.HasErrors())
204214
return
205215
}
216+
if diags.HasErrors() {
217+
t.Logf("diags: %s", diags)
218+
}
206219
require.False(t, diags.HasErrors())
207220

208221
// Assert tags

Diff for: ‎testdata/manymodules/main.tf

+31-1
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,38 @@ locals {
1919
}
2020

2121
data "coder_parameter" "mainquestion" {
22-
name = "Two Question"
22+
name = "Main Question"
2323
description = "From module 2"
2424
type = "string"
2525
default = local.foo
26+
27+
option {
28+
name = "Default"
29+
value = local.foo
30+
}
31+
32+
option {
33+
name = "Primary"
34+
value = module.one.export
35+
}
36+
37+
option {
38+
name = "Second"
39+
value = module.two.export
40+
}
41+
42+
option {
43+
name = "Terraform"
44+
value = module.one.terraform
45+
}
46+
47+
option {
48+
name = "Consul"
49+
value = module.two.consul
50+
}
51+
52+
option {
53+
name = "Packer"
54+
value = module.one.export-a
55+
}
2656
}

Diff for: ‎testdata/manymodules/one/one.tf

+40
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,44 @@ data "coder_parameter" "onequestion" {
1515
description = "From module 1"
1616
type = "string"
1717
default = local.foo
18+
19+
option {
20+
name = "Default"
21+
value = local.foo
22+
}
23+
24+
option {
25+
name = "Primary Sub A"
26+
value = module.onea.export
27+
}
28+
29+
option {
30+
name = "Terraform"
31+
value = jsondecode(data.http.terraform.response_body).current_version
32+
}
33+
}
34+
35+
module "onea" {
36+
source = "./onea"
37+
}
38+
39+
output "export" {
40+
value = local.foo
41+
}
42+
43+
output "export-a" {
44+
value = module.onea.export
45+
}
46+
47+
output "terraform" {
48+
value = jsondecode(data.http.terraform.response_body).current_version
49+
}
50+
51+
data "http" "terraform" {
52+
url = "https://checkpoint-api.hashicorp.com/v1/check/terraform"
53+
54+
# Optional request headers
55+
request_headers = {
56+
Accept = "application/json"
57+
}
1858
}

Diff for: ‎testdata/manymodules/one/onea/onea.tf

+12
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ data "coder_parameter" "one-a-question" {
2525
name = "Terraform"
2626
value = jsondecode(data.http.packer.response_body).current_version
2727
}
28+
29+
option {
30+
name = "NullResource"
31+
value = data.null_data_source.values.outputs["foo"]
32+
}
2833
}
2934

3035
output "export" {
@@ -41,5 +46,12 @@ data "http" "packer" {
4146
# Optional request headers
4247
request_headers = {
4348
Accept = "application/json"
49+
Arbitrary = data.null_data_source.values.outputs["foo"]
50+
}
51+
}
52+
53+
data "null_data_source" "values" {
54+
inputs = {
55+
foo = "bar"
4456
}
4557
}

Diff for: ‎testdata/manymodules/out.json

+849
Large diffs are not rendered by default.

Diff for: ‎testdata/manymodules/two/two.tf

+27
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,31 @@ data "coder_parameter" "twoquestion" {
1515
description = "From module 2"
1616
type = "string"
1717
default = local.foo
18+
19+
option {
20+
name = "Default"
21+
value = local.foo
22+
}
23+
24+
option {
25+
name = "Consul"
26+
value = jsondecode(data.http.consul.response_body).current_version
27+
}
28+
}
29+
30+
output "export" {
31+
value = local.foo
32+
}
33+
34+
output "consul" {
35+
value = jsondecode(data.http.consul.response_body).current_version
36+
}
37+
38+
data "http" "consul" {
39+
url = "https://checkpoint-api.hashicorp.com/v1/check/consul"
40+
41+
# Optional request headers
42+
request_headers = {
43+
Accept = "application/json"
44+
}
1845
}

0 commit comments

Comments
 (0)
Please sign in to comment.