Skip to content

Commit 6c6beea

Browse files
authored
chore(terraform): option to pass in instanced logger (#8738)
1 parent 2849abb commit 6c6beea

File tree

4 files changed

+143
-11
lines changed

4 files changed

+143
-11
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ func (e *evaluator) loadExternalModule(ctx context.Context, b *terraform.Block,
143143
WorkingDir: e.projectRootPath,
144144
Name: b.FullName(),
145145
ModulePath: e.modulePath,
146-
Logger: log.WithPrefix("module resolver"),
146+
Logger: e.logger.With(log.Prefix("module resolver")),
147147
AllowDownloads: e.allowDownloads,
148148
SkipCache: e.skipCachedModules,
149149
}

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

+8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"io/fs"
55

66
"github.com/zclconf/go-cty/cty"
7+
8+
"github.com/aquasecurity/trivy/pkg/log"
79
)
810

911
type Option func(p *Parser)
@@ -20,6 +22,12 @@ func OptionStopOnHCLError(stop bool) Option {
2022
}
2123
}
2224

25+
func OptionWithLogger(log *log.Logger) Option {
26+
return func(p *Parser) {
27+
p.logger = log
28+
}
29+
}
30+
2331
func OptionsWithTfVars(vars map[string]cty.Value) Option {
2432
return func(p *Parser) {
2533
p.tfvars = vars

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

+11-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"io"
99
"io/fs"
10+
"log/slog"
1011
"os"
1112
"path"
1213
"path/filepath"
@@ -64,14 +65,17 @@ func New(moduleFS fs.FS, moduleSource string, opts ...Option) *Parser {
6465
moduleFS: moduleFS,
6566
moduleSource: moduleSource,
6667
configsFS: moduleFS,
67-
logger: log.WithPrefix("terraform parser").With("module", "root"),
68+
logger: slog.Default(),
6869
tfvars: make(map[string]cty.Value),
6970
}
7071

7172
for _, option := range opts {
7273
option(p)
7374
}
7475

76+
// Scope the logger to the parser
77+
p.logger = p.logger.With(log.Prefix("terraform parser")).With("module", "root")
78+
7579
return p
7680
}
7781

@@ -80,14 +84,18 @@ func (p *Parser) newModuleParser(moduleFS fs.FS, moduleSource, modulePath, modul
8084
mp.modulePath = modulePath
8185
mp.moduleBlock = moduleBlock
8286
mp.moduleName = moduleName
83-
mp.logger = log.WithPrefix("terraform parser").With("module", moduleName)
87+
mp.logger = p.logger
8488
mp.projectRoot = p.projectRoot
8589
mp.skipPaths = p.skipPaths
8690
mp.options = p.options
8791
p.children = append(p.children, mp)
8892
for _, option := range p.options {
8993
option(mp)
9094
}
95+
96+
// The options above can reset the logger, so set the logging prefix after the
97+
// options.
98+
mp.logger = mp.logger.With(log.Prefix("terraform parser")).With("module", moduleName)
9199
return mp
92100
}
93101

@@ -303,7 +311,7 @@ func (p *Parser) Load(ctx context.Context) (*evaluator, error) {
303311
modulesMetadata,
304312
p.workspaceName,
305313
ignores,
306-
log.WithPrefix("terraform evaluator"),
314+
p.logger.With(log.Prefix("terraform evaluator")),
307315
p.allowDownloads,
308316
p.skipCachedModules,
309317
), nil

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

+123-7
Original file line numberDiff line numberDiff line change
@@ -2374,9 +2374,6 @@ variable "baz" {}
23742374
}
23752375

23762376
func TestLoadChildModulesFromLocalCache(t *testing.T) {
2377-
var buf bytes.Buffer
2378-
slog.SetDefault(slog.New(log.NewHandler(&buf, &log.Options{Level: log.LevelDebug})))
2379-
23802377
fsys := fstest.MapFS{
23812378
"main.tf": &fstest.MapFile{Data: []byte(`module "level_1" {
23822379
source = "./modules/level_1"
@@ -2411,9 +2408,13 @@ func TestLoadChildModulesFromLocalCache(t *testing.T) {
24112408
}`)},
24122409
}
24132410

2411+
var buf bytes.Buffer
2412+
logger := slog.New(log.NewHandler(&buf, &log.Options{Level: log.LevelDebug}))
2413+
24142414
parser := New(
24152415
fsys, "",
24162416
OptionStopOnHCLError(true),
2417+
OptionWithLogger(logger),
24172418
)
24182419
require.NoError(t, parser.ParseFS(t.Context(), "."))
24192420

@@ -2422,10 +2423,10 @@ func TestLoadChildModulesFromLocalCache(t *testing.T) {
24222423

24232424
assert.Len(t, modules, 5)
24242425

2425-
assert.Contains(t, buf.String(), "Using module from Terraform cache .terraform/modules\tsource=\"./modules/level_1\"")
2426-
assert.Contains(t, buf.String(), "Using module from Terraform cache .terraform/modules\tsource=\"../level_2\"")
2427-
assert.Contains(t, buf.String(), "Using module from Terraform cache .terraform/modules\tsource=\"../level_3\"")
2428-
assert.Contains(t, buf.String(), "Using module from Terraform cache .terraform/modules\tsource=\"../level_3\"")
2426+
assert.Contains(t, buf.String(), "Using module from Terraform cache .terraform/modules\tmodule=\"root\" source=\"./modules/level_1\"")
2427+
assert.Contains(t, buf.String(), "Using module from Terraform cache .terraform/modules\tmodule=\"level_1\" source=\"../level_2\"")
2428+
assert.Contains(t, buf.String(), "Using module from Terraform cache .terraform/modules\tmodule=\"level_2\" source=\"../level_3\"")
2429+
assert.Contains(t, buf.String(), "Using module from Terraform cache .terraform/modules\tmodule=\"level_2\" source=\"../level_3\"")
24292430
}
24302431

24312432
func TestNilParser(t *testing.T) {
@@ -2633,3 +2634,118 @@ resource "foo" "bar" {
26332634
attr := foo.GetAttribute("attr")
26342635
assert.False(t, attr.IsResolvable())
26352636
}
2637+
2638+
// TestInstancedLogger checks if any global logs are generated by the terraform
2639+
// parser + evaluation. All logs should be attached to the instanced logger. This
2640+
// test does not run all error cases, so it will not capture all log output.
2641+
func TestInstancedLogger(t *testing.T) {
2642+
// reset global logger
2643+
prevLog := slog.Default()
2644+
defer slog.SetDefault(prevLog)
2645+
2646+
// capture logs to the global logger
2647+
var buf bytes.Buffer
2648+
2649+
slog.SetDefault(slog.New(log.NewHandler(&buf, &log.Options{
2650+
Level: slog.LevelDebug,
2651+
})))
2652+
2653+
// create a new logger for the parser
2654+
var instance bytes.Buffer
2655+
logger := slog.New(log.NewHandler(&instance, &log.Options{
2656+
Level: slog.LevelDebug,
2657+
}))
2658+
2659+
opts := []Option{
2660+
OptionWithLogger(logger),
2661+
OptionStopOnHCLError(false),
2662+
OptionWithDownloads(false),
2663+
}
2664+
// Run some scenarios that will trigger logs
2665+
t.Run("ParserLogs", func(t *testing.T) {
2666+
fsys := fstest.MapFS{
2667+
"main.tf": &fstest.MapFile{
2668+
Data: []byte(`
2669+
bare words
2670+
`)},
2671+
}
2672+
2673+
parser := New(fsys, "", opts...)
2674+
2675+
// No error is returned, but some parser logs are expected
2676+
err := parser.ParseFS(t.Context(), ".")
2677+
require.NoError(t, err)
2678+
require.NotEmpty(t, instance.String())
2679+
instance.Reset()
2680+
})
2681+
2682+
t.Run("Evaluator", func(t *testing.T) {
2683+
fsys := fstest.MapFS{
2684+
"main.tf": &fstest.MapFile{
2685+
Data: []byte(`
2686+
locals {
2687+
phrase = "hello world"
2688+
}
2689+
2690+
output "phrase" {
2691+
value = locals.phrase
2692+
}
2693+
`)},
2694+
}
2695+
2696+
parser := New(fsys, "", opts...)
2697+
2698+
err := parser.ParseFS(t.Context(), ".")
2699+
require.NoError(t, err)
2700+
2701+
_, err = parser.EvaluateAll(t.Context())
2702+
require.NoError(t, err)
2703+
2704+
require.NotEmpty(t, instance.String())
2705+
instance.Reset()
2706+
})
2707+
2708+
t.Run("ModuleFetching", func(t *testing.T) {
2709+
fsys := testutil.CreateFS(t, map[string]string{
2710+
"main.tf": `
2711+
module "invalid" {
2712+
source = "totally.invalid"
2713+
}
2714+
2715+
module "echo" {
2716+
source = "./echo"
2717+
input = "hello"
2718+
}
2719+
2720+
locals {
2721+
foo = module.echo.value
2722+
}
2723+
`,
2724+
"echo/main.tf": `
2725+
variable "input" {
2726+
type = string
2727+
}
2728+
output "value" {
2729+
value = var.input
2730+
}
2731+
`,
2732+
})
2733+
2734+
parser := New(fsys, "", opts...)
2735+
2736+
err := parser.ParseFS(t.Context(), ".")
2737+
require.NoError(t, err)
2738+
2739+
modules, err := parser.EvaluateAll(t.Context())
2740+
require.NoError(t, err)
2741+
2742+
require.NotEmpty(t, instance.String())
2743+
require.Len(t, modules, 2)
2744+
instance.Reset()
2745+
})
2746+
2747+
//nolint:testifylint // linter wants `emptyf`, but the output is not legible
2748+
if !assert.Lenf(t, buf.Bytes(), 0, "logs detected in global logger, all logs should be using the instanced logger") {
2749+
t.Log(string(buf.Bytes())) // Helpful for debugging
2750+
}
2751+
}

0 commit comments

Comments
 (0)