Skip to content

Commit 1319d8d

Browse files
authored
fix(misconf): preserve original paths of remote submodules from .terraform (#9294)
Signed-off-by: nikpivkin <nikita.pivkin@smartforce.io>
1 parent c0bd700 commit 1319d8d

File tree

3 files changed

+65
-21
lines changed

3 files changed

+65
-21
lines changed

pkg/iac/scanners/terraform/parser/load_module.go

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package parser
22

33
import (
44
"context"
5-
"errors"
65
"fmt"
76
"io/fs"
87
"path"
@@ -78,9 +77,19 @@ func (e *evaluator) loadModule(ctx context.Context, b *terraform.Block) (*Module
7877
return nil, fmt.Errorf("could not read module source attribute at %s", metadata.Range().String())
7978
}
8079

81-
if def, err := e.loadModuleFromTerraformCache(ctx, b, source); err == nil {
82-
e.logger.Debug("Using module from Terraform cache .terraform/modules", log.String("source", source))
83-
return def, nil
80+
if e.moduleMetadata != nil {
81+
// if we have module metadata we can parse all the modules as they'll be cached locally!
82+
def, err := e.loadModuleFromTerraformCache(ctx, b, source)
83+
if err == nil {
84+
e.logger.Debug("Using module from Terraform cache .terraform/modules",
85+
log.String("source", source))
86+
return def, nil
87+
}
88+
89+
e.logger.Debug("Failed to load module from .terraform cache",
90+
log.String("module_source", source),
91+
log.Err(err),
92+
)
8493
}
8594

8695
// we don't have the module installed via 'terraform init' so we need to grab it...
@@ -89,38 +98,31 @@ func (e *evaluator) loadModule(ctx context.Context, b *terraform.Block) (*Module
8998

9099
func (e *evaluator) loadModuleFromTerraformCache(ctx context.Context, b *terraform.Block, source string) (*ModuleDefinition, error) {
91100
var modulePath string
92-
if e.moduleMetadata != nil {
93-
// if we have module metadata we can parse all the modules as they'll be cached locally!
94-
moduleKey := b.ModuleKey()
95-
for _, module := range e.moduleMetadata.Modules {
96-
if module.Key == moduleKey {
97-
modulePath = path.Clean(path.Join(e.projectRootPath, module.Dir))
98-
break
99-
}
101+
moduleKey := b.ModuleKey()
102+
for _, module := range e.moduleMetadata.Modules {
103+
if module.Key == moduleKey {
104+
modulePath = path.Clean(path.Join(e.projectRootPath, module.Dir))
105+
break
100106
}
101107
}
102108
if modulePath == "" {
103-
return nil, errors.New("failed to load module from .terraform/modules")
109+
return nil, fmt.Errorf("resolve module with key %q from .terraform/modules", moduleKey)
104110
}
111+
105112
if strings.HasPrefix(source, ".") {
106113
source = ""
107114
}
108115

109-
if prefix, relativeDir, ok := strings.Cut(source, "//"); ok && !strings.HasSuffix(prefix, ":") && strings.Count(prefix, "/") == 2 {
110-
if !strings.HasSuffix(modulePath, relativeDir) {
111-
modulePath = fmt.Sprintf("%s/%s", modulePath, relativeDir)
112-
}
113-
}
114-
115116
e.logger.Debug("Module resolved using modules.json",
116117
log.String("block", b.FullName()),
117118
log.String("source", source),
118119
log.String("modulePath", modulePath),
119120
)
120121
moduleParser := e.parentParser.newModuleParser(e.filesystem, source, modulePath, b.Label(), b)
121122
if err := moduleParser.ParseFS(ctx, modulePath); err != nil {
122-
return nil, err
123+
return nil, fmt.Errorf("parse module filesystem: %w", err)
123124
}
125+
124126
return &ModuleDefinition{
125127
Name: b.Label(),
126128
Path: modulePath,

pkg/iac/scanners/terraform/parser/parser.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ func (p *Parser) ParseFS(ctx context.Context, dir string) error {
169169
p.logger.Debug("Parsing FS", log.FilePath(slashed))
170170
fileInfos, err := fs.ReadDir(p.moduleFS, slashed)
171171
if err != nil {
172-
return err
172+
return fmt.Errorf("read dir: %w", err)
173173
}
174174

175175
var paths []string

pkg/iac/scanners/terraform/parser/parser_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2812,3 +2812,45 @@ func TestProvidedWorkingDirectory(t *testing.T) {
28122812
attr := foo.GetAttribute("cwd")
28132813
require.Equal(t, fakeCwd, attr.Value().AsString())
28142814
}
2815+
2816+
func Test_ResolveRemoteSubmoduleFromTerraformCache(t *testing.T) {
2817+
fsys := fstest.MapFS{
2818+
"main.tf": &fstest.MapFile{Data: []byte(`module "aws_bucket" {
2819+
source = "github.com/foo/bar.git//aws/modules/bucket?ref=v0.0.1"
2820+
}
2821+
2822+
locals {
2823+
test = module.aws_bucket.out
2824+
}
2825+
`)},
2826+
".terraform/modules/modules.json": &fstest.MapFile{Data: []byte(`{
2827+
"Modules": [
2828+
{ "Key": "", "Source": "", "Dir": "." },
2829+
{
2830+
"Key": "aws_bucket",
2831+
"Source": "git::https://github.com/foo/bar.git//aws/modules/bucket?ref=v0.0.1",
2832+
"Dir": ".terraform/modules/aws_bucket/aws/modules/bucket"
2833+
}
2834+
]
2835+
}`)},
2836+
".terraform/modules/aws_bucket/aws/modules/bucket/main.tf": &fstest.MapFile{Data: []byte(`output "out" {
2837+
value = "some_value"
2838+
}`)},
2839+
}
2840+
2841+
parser := New(fsys, "",
2842+
OptionWithSkipCachedModules(true),
2843+
OptionWithDownloads(false),
2844+
OptionStopOnHCLError(true),
2845+
)
2846+
err := parser.ParseFS(t.Context(), ".")
2847+
require.NoError(t, err)
2848+
2849+
modules, err := parser.EvaluateAll(t.Context())
2850+
require.NoError(t, err)
2851+
2852+
require.Len(t, modules, 2)
2853+
bucket := modules[0].GetBlocks().OfType("locals")[0]
2854+
attr := bucket.GetAttribute("test")
2855+
require.Equal(t, "some_value", attr.Value().AsString())
2856+
}

0 commit comments

Comments
 (0)