Files
trivy/pkg/iac/scanners/terraform/parser/modules.go
2024-05-04 04:24:39 +00:00

83 lines
1.9 KiB
Go

package parser
import (
"context"
"path"
"sort"
"strings"
"github.com/samber/lo"
"github.com/zclconf/go-cty/cty"
"github.com/aquasecurity/trivy/pkg/iac/terraform"
)
// FindRootModules takes a list of module paths and identifies the root local modules.
// It builds a graph based on the module dependencies and determines the modules that have no incoming dependencies,
// considering them as root modules.
func (p *Parser) FindRootModules(ctx context.Context, dirs []string) ([]string, error) {
// skip cached terraform modules as they cannot be root modules
dirs = lo.Filter(dirs, func(dir string, _ int) bool {
return !strings.Contains(dir, ".terraform/modules/")
})
for _, dir := range dirs {
if err := p.ParseFS(ctx, dir); err != nil {
return nil, err
}
}
blocks, _, err := p.readBlocks(p.files)
if err != nil {
return nil, err
}
g := buildGraph(blocks, dirs)
rootModules := g.rootModules()
sort.Strings(rootModules)
return rootModules, nil
}
type modulesGraph map[string][]string
func buildGraph(blocks terraform.Blocks, paths []string) modulesGraph {
moduleBlocks := blocks.OfType("module")
graph := lo.SliceToMap(paths, func(p string) (string, []string) {
return p, nil
})
for _, block := range moduleBlocks {
sourceVal := block.GetAttribute("source").Value()
if sourceVal.Type() != cty.String {
continue
}
source := sourceVal.AsString()
if strings.HasPrefix(source, ".") {
filename := block.GetMetadata().Range().GetFilename()
dir := path.Dir(filename)
graph[dir] = append(graph[dir], path.Join(dir, source))
}
}
return graph
}
func (g modulesGraph) rootModules() []string {
incomingEdges := make(map[string]int)
for _, neighbors := range g {
for _, neighbor := range neighbors {
incomingEdges[neighbor]++
}
}
var roots []string
for module := range g {
if incomingEdges[module] == 0 {
roots = append(roots, module)
}
}
return roots
}