mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-22 07:10:41 -08:00
chore(terraform): assign *terraform.Module 'parent' field (#8444)
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
@@ -18,6 +19,7 @@ import (
|
||||
"github.com/aquasecurity/trivy/internal/testutil"
|
||||
"github.com/aquasecurity/trivy/pkg/iac/terraform"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/set"
|
||||
)
|
||||
|
||||
func Test_BasicParsing(t *testing.T) {
|
||||
@@ -1714,6 +1716,20 @@ func TestNestedModulesOptions(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
slog.SetDefault(slog.New(log.NewHandler(&buf, nil)))
|
||||
|
||||
// Folder structure
|
||||
// ./
|
||||
// ├── main.tf
|
||||
// └── modules
|
||||
// ├── city
|
||||
// │ └── main.tf
|
||||
// ├── queens
|
||||
// │ └── main.tf
|
||||
// └── brooklyn
|
||||
// └── main.tf
|
||||
//
|
||||
// Modules referenced
|
||||
// main -> city ├─> brooklyn
|
||||
// └─> queens
|
||||
files := map[string]string{
|
||||
"main.tf": `
|
||||
module "city" {
|
||||
@@ -1769,6 +1785,140 @@ module "invalid" {
|
||||
}
|
||||
|
||||
require.NotContains(t, buf.String(), "failed to download")
|
||||
|
||||
// Verify module parents are set correctly.
|
||||
expectedParents := map[string]string{
|
||||
".": "",
|
||||
"modules/city": ".",
|
||||
"modules/city/brooklyn": "modules/city",
|
||||
"modules/city/queens": "modules/city",
|
||||
}
|
||||
|
||||
for _, mod := range modules {
|
||||
expected, exists := expectedParents[mod.ModulePath()]
|
||||
require.Truef(t, exists, "module %s does not exist in assertion", mod.ModulePath())
|
||||
if expected == "" {
|
||||
require.Nil(t, mod.Parent())
|
||||
} else {
|
||||
require.Equal(t, expected, mod.Parent().ModulePath(), "parent of module %q", mod.ModulePath())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestModuleParents sets up a nested module structure and verifies the
|
||||
// parent-child relationships are correctly set.
|
||||
func TestModuleParents(t *testing.T) {
|
||||
// The setup is a list of continents, some countries, some cities, etc.
|
||||
dirfs := os.DirFS("./testdata/nested")
|
||||
parser := New(dirfs, "",
|
||||
OptionStopOnHCLError(true),
|
||||
OptionWithDownloads(false),
|
||||
)
|
||||
require.NoError(t, parser.ParseFS(context.TODO(), "."))
|
||||
|
||||
modules, _, err := parser.EvaluateAll(context.TODO())
|
||||
require.NoError(t, err)
|
||||
|
||||
// modules only have 'parent'. They do not have children, so create
|
||||
// a structure that allows traversal from the root to the leafs.
|
||||
modChildren := make(map[*terraform.Module][]*terraform.Module)
|
||||
// Keep track of every module that exists
|
||||
modSet := set.New[*terraform.Module]()
|
||||
var root *terraform.Module
|
||||
for _, mod := range modules {
|
||||
mod := mod
|
||||
modChildren[mod] = make([]*terraform.Module, 0)
|
||||
modSet.Append(mod)
|
||||
|
||||
if mod.Parent() == nil {
|
||||
// Only 1 root should exist
|
||||
require.Nil(t, root, "root module already set")
|
||||
root = mod
|
||||
}
|
||||
modChildren[mod.Parent()] = append(modChildren[mod.Parent()], mod)
|
||||
}
|
||||
|
||||
type node struct {
|
||||
prefix string
|
||||
modulePath string
|
||||
children []node
|
||||
}
|
||||
|
||||
// expectedTree is the full module tree structure.
|
||||
expectedTree := node{
|
||||
modulePath: ".",
|
||||
children: []node{
|
||||
{
|
||||
modulePath: "north-america",
|
||||
children: []node{
|
||||
{
|
||||
modulePath: "north-america/united-states",
|
||||
children: []node{
|
||||
{modulePath: "north-america/united-states/springfield", prefix: "illinois-"},
|
||||
{modulePath: "north-america/united-states/springfield", prefix: "idaho-"},
|
||||
{modulePath: "north-america/united-states/new-york", children: []node{
|
||||
{modulePath: "north-america/united-states/new-york/new-york-city"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
modulePath: "north-america/canada",
|
||||
children: []node{
|
||||
{modulePath: "north-america/canada/springfield", prefix: "ontario-"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var assertChild func(t *testing.T, n node, mod *terraform.Module)
|
||||
assertChild = func(t *testing.T, n node, mod *terraform.Module) {
|
||||
modSet.Remove(mod)
|
||||
children := modChildren[mod]
|
||||
|
||||
t.Run(n.modulePath, func(t *testing.T) {
|
||||
if !assert.Equal(t, len(n.children), len(children), "modChildren count for %s", n.modulePath) {
|
||||
return
|
||||
}
|
||||
for _, child := range children {
|
||||
// Find the child module that we are expecting.
|
||||
idx := slices.IndexFunc(n.children, func(node node) bool {
|
||||
outputBlocks := child.GetBlocks().OfType("output")
|
||||
outIdx := slices.IndexFunc(outputBlocks, func(outputBlock *terraform.Block) bool {
|
||||
return outputBlock.Labels()[0] == "name"
|
||||
})
|
||||
if outIdx == -1 {
|
||||
return false
|
||||
}
|
||||
|
||||
output := outputBlocks[outIdx]
|
||||
outVal := output.GetAttribute("value").Value()
|
||||
if !outVal.Type().Equals(cty.String) {
|
||||
return false
|
||||
}
|
||||
|
||||
modName := filepath.Base(node.modulePath)
|
||||
if outVal.AsString() != node.prefix+modName {
|
||||
return false
|
||||
}
|
||||
|
||||
return node.modulePath == child.ModulePath()
|
||||
})
|
||||
if !assert.NotEqualf(t, -1, idx, "module prefix=%s path=%s not found in %s", n.prefix, child.ModulePath(), n.modulePath) {
|
||||
continue
|
||||
}
|
||||
|
||||
assertChild(t, n.children[idx], child)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
assertChild(t, expectedTree, root)
|
||||
// If any module was not asserted, the test will fail. This ensures the
|
||||
// entire module tree is checked.
|
||||
require.Equal(t, 0, modSet.Size(), "all modules asserted")
|
||||
}
|
||||
|
||||
func TestCyclicModules(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user