Skip to content

Commit 29428a0

Browse files
committed
Added passing of parser options
1 parent 44ae458 commit 29428a0

File tree

6 files changed

+116
-21
lines changed

6 files changed

+116
-21
lines changed

internal/discovery/discovery.go

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ const (
2929
ConfigTypeUnit ConfigType = "unit"
3030
// ConfigTypeStack is the type of Terragrunt configuration for a stack.
3131
ConfigTypeStack ConfigType = "stack"
32+
33+
skipOutputDiagnostics = "output"
3234
)
3335

3436
// ConfigType is the type of Terragrunt configuration.
@@ -116,6 +118,9 @@ type Discovery struct {
116118

117119
// excludeByDefault determines whether to exclude configurations by default (triggered by include flags).
118120
excludeByDefault bool
121+
122+
// parserOptions are custom HCL parser options to use when parsing during discovery
123+
parserOptions []hclparse.Option
119124
}
120125

121126
// DiscoveryOption is a function that modifies a Discovery.
@@ -140,6 +145,26 @@ func NewDiscovery(dir string, opts ...DiscoveryOption) *Discovery {
140145
return discovery
141146
}
142147

148+
// parseOptionsProvider is a narrow interface used to extract parser options from
149+
// option values without introducing a dependency on runner or stack packages.
150+
type parseOptionsProvider interface {
151+
GetParseOptions() ([]hclparse.Option, bool)
152+
}
153+
154+
// WithOptions applies any provided options that expose parser options.
155+
// Accepts any option types; only those implementing GetParseOptions are used.
156+
func (d *Discovery) WithOptions(opts ...any) *Discovery { //nolint: revive
157+
for _, opt := range opts {
158+
if provider, ok := opt.(parseOptionsProvider); ok {
159+
if parseOpts, ok := provider.GetParseOptions(); ok {
160+
d = d.WithParserOptions(parseOpts)
161+
}
162+
}
163+
}
164+
165+
return d
166+
}
167+
143168
// WithHidden sets the Hidden flag to true.
144169
func (d *Discovery) WithHidden() *Discovery {
145170
d.hidden = true
@@ -243,6 +268,12 @@ func (d *Discovery) WithExcludeByDefault() *Discovery {
243268
return d
244269
}
245270

271+
// WithParserOptions sets custom parser options to be used when parsing configs during discovery.
272+
func (d *Discovery) WithParserOptions(options []hclparse.Option) *Discovery {
273+
d.parserOptions = options
274+
return d
275+
}
276+
246277
// String returns a string representation of a DiscoveredConfig.
247278
func (c *DiscoveredConfig) String() string {
248279
return c.Path
@@ -265,7 +296,7 @@ func (c *DiscoveredConfig) ContainsDependencyInAncestry(path string) bool {
265296
}
266297

267298
// Parse parses the discovered configurations.
268-
func (c *DiscoveredConfig) Parse(ctx context.Context, l log.Logger, opts *options.TerragruntOptions, suppressParseErrors bool) error {
299+
func (c *DiscoveredConfig) Parse(ctx context.Context, l log.Logger, opts *options.TerragruntOptions, suppressParseErrors bool, parserOptions []hclparse.Option) error {
269300
parseOpts := opts.Clone()
270301
parseOpts.WorkingDir = c.Path
271302

@@ -292,15 +323,20 @@ func (c *DiscoveredConfig) Parse(ctx context.Context, l log.Logger, opts *option
292323
config.ExcludeBlock,
293324
)
294325

326+
// Apply any custom parser options first
327+
if len(parserOptions) > 0 {
328+
parsingCtx = parsingCtx.WithParseOption(append(parsingCtx.ParserOptions, parserOptions...))
329+
}
330+
295331
if suppressParseErrors {
296332
// If suppressing parse errors, we want to filter diagnostics that contain references to outputs,
297333
// while leaving other diagnostics as is.
298334
parseOptions := append(parsingCtx.ParserOptions, hclparse.WithDiagnosticsHandler(func(file *hcl.File, hclDiags hcl.Diagnostics) (hcl.Diagnostics, error) {
299335
filteredDiags := hcl.Diagnostics{}
300336

301337
for _, hclDiag := range hclDiags {
302-
containsOutputRef := strings.Contains(strings.ToLower(hclDiag.Summary), "output") ||
303-
strings.Contains(strings.ToLower(hclDiag.Detail), "output")
338+
containsOutputRef := strings.Contains(strings.ToLower(hclDiag.Summary), skipOutputDiagnostics) ||
339+
strings.Contains(strings.ToLower(hclDiag.Detail), skipOutputDiagnostics)
304340

305341
if !containsOutputRef {
306342
filteredDiags = append(filteredDiags, hclDiag)
@@ -557,7 +593,7 @@ func (d *Discovery) Discover(ctx context.Context, l log.Logger, opts *options.Te
557593
continue
558594
}
559595

560-
err := cfg.Parse(ctx, l, opts, d.suppressParseErrors)
596+
err := cfg.Parse(ctx, l, opts, d.suppressParseErrors, d.parserOptions)
561597
if err != nil {
562598
errs = append(errs, errors.New(err))
563599
}
@@ -585,6 +621,10 @@ func (d *Discovery) Discover(ctx context.Context, l log.Logger, opts *options.Te
585621
dependencyDiscovery = dependencyDiscovery.WithSuppressParseErrors()
586622
}
587623

624+
if len(d.parserOptions) > 0 {
625+
dependencyDiscovery = dependencyDiscovery.WithParserOptions(d.parserOptions)
626+
}
627+
588628
// Pass include patterns and strict mode to dependency discovery
589629
if len(d.includeDirs) > 0 {
590630
dependencyDiscovery = dependencyDiscovery.WithIncludeDirs(d.includeDirs)
@@ -647,6 +687,7 @@ type DependencyDiscovery struct {
647687
discoverExternal bool
648688
suppressParseErrors bool
649689
strictInclude bool
690+
parserOptions []hclparse.Option
650691
}
651692

652693
// DependencyDiscoveryOption is a function that modifies a DependencyDiscovery.
@@ -691,6 +732,12 @@ func (d *DependencyDiscovery) WithStrictInclude() *DependencyDiscovery {
691732
return d
692733
}
693734

735+
// WithParserOptions sets custom parser options to be used when parsing dependency configs during discovery.
736+
func (d *DependencyDiscovery) WithParserOptions(options []hclparse.Option) *DependencyDiscovery {
737+
d.parserOptions = options
738+
return d
739+
}
740+
694741
// matchesIncludePatterns returns true if the path matches any of the include directory patterns.
695742
func (d *DependencyDiscovery) matchesIncludePatterns(path string) bool {
696743
if len(d.includeDirs) == 0 {
@@ -752,7 +799,7 @@ func (d *DependencyDiscovery) DiscoverDependencies(ctx context.Context, l log.Lo
752799

753800
// This should only happen if we're discovering an ancestor dependency.
754801
if dCfg.Parsed == nil {
755-
err := dCfg.Parse(ctx, l, opts, d.suppressParseErrors)
802+
err := dCfg.Parse(ctx, l, opts, d.suppressParseErrors, d.parserOptions)
756803
if err != nil {
757804
return errors.New(err)
758805
}

internal/runner/common/options.go

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,68 @@ import (
66
"github.com/gruntwork-io/terragrunt/internal/report"
77
)
88

9-
// Option is a function that modifies a Stack.
10-
type Option func(StackRunner)
9+
// ParseOptionsSetter is a minimal interface for types that can accept parser options.
10+
// Both stack runners and discovery implement SetParseOptions with this signature.
11+
type ParseOptionsSetter interface {
12+
SetParseOptions(parserOptions []hclparse.Option)
13+
}
14+
15+
// Option applies configuration to the StackRunner.
16+
type Option interface {
17+
ApplyStack(stack StackRunner)
18+
}
19+
20+
type optionImpl struct {
21+
applyStack func(StackRunner)
22+
parserOptions []hclparse.Option
23+
hasParseOptions bool
24+
}
25+
26+
func (o optionImpl) ApplyStack(stack StackRunner) {
27+
if o.applyStack != nil {
28+
o.applyStack(stack)
29+
}
30+
}
31+
32+
// ParseOptionsProvider allows extracting parser options from an Option without applying it to a stack.
33+
type ParseOptionsProvider interface {
34+
GetParseOptions() ([]hclparse.Option, bool)
35+
}
36+
37+
// GetParseOptions returns parser options when the option was created by WithParseOptions.
38+
func (o optionImpl) GetParseOptions() ([]hclparse.Option, bool) {
39+
if o.hasParseOptions {
40+
return o.parserOptions, true
41+
}
42+
43+
return nil, false
44+
}
1145

1246
// WithChildTerragruntConfig sets the TerragruntConfig on any Stack implementation.
13-
func WithChildTerragruntConfig(config *config.TerragruntConfig) Option {
14-
return func(stack StackRunner) {
15-
stack.SetTerragruntConfig(config)
47+
func WithChildTerragruntConfig(cfg *config.TerragruntConfig) Option {
48+
return optionImpl{
49+
applyStack: func(stack StackRunner) {
50+
stack.SetTerragruntConfig(cfg)
51+
},
1652
}
1753
}
1854

19-
// WithParseOptions sets the parserOptions on any Stack implementation.
55+
// WithParseOptions sets custom HCL parser options on both discovery and stack.
2056
func WithParseOptions(parserOptions []hclparse.Option) Option {
21-
return func(stack StackRunner) {
22-
stack.SetParseOptions(parserOptions)
57+
return optionImpl{
58+
applyStack: func(stack StackRunner) {
59+
stack.SetParseOptions(parserOptions)
60+
},
61+
parserOptions: parserOptions,
62+
hasParseOptions: true,
2363
}
2464
}
2565

26-
func WithReport(report *report.Report) Option {
27-
return func(stack StackRunner) {
28-
stack.SetReport(report)
66+
// WithReport attaches a report collector to the stack only.
67+
func WithReport(r *report.Report) Option {
68+
return optionImpl{
69+
applyStack: func(stack StackRunner) {
70+
stack.SetReport(r)
71+
},
2972
}
3073
}

internal/runner/configstack/runner.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func NewRunner(l log.Logger, terragruntOptions *options.TerragruntOptions, opts
5050
// WithOptions updates the stack with the provided options.
5151
func (runner *Runner) WithOptions(opts ...common.Option) *Runner {
5252
for _, opt := range opts {
53-
opt(runner)
53+
opt.ApplyStack(runner)
5454
}
5555

5656
return runner

internal/runner/runnerpool/builder.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ func Build(ctx context.Context, l log.Logger, terragruntOptions *options.Terragr
1717
// discovery configurations
1818
d := discovery.
1919
NewDiscovery(terragruntOptions.WorkingDir).
20+
WithOptions(opts).
2021
WithDiscoverExternalDependencies().
2122
WithParseInclude().
2223
WithParseExclude().

internal/runner/runnerpool/runner.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ func (r *Runner) summarizePlanAllErrors(l log.Logger, errorStreams []bytes.Buffe
290290
// WithOptions updates the stack with the provided options.
291291
func (r *Runner) WithOptions(opts ...common.Option) *Runner {
292292
for _, opt := range opts {
293-
opt(r)
293+
opt.ApplyStack(r)
294294
}
295295

296296
return r

test/integration_test.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -727,9 +727,13 @@ func TestHclvalidateReturnsNonZeroExitCodeOnError(t *testing.T) {
727727
_, _, err := helpers.RunTerragruntCommandWithOutput(t, "terragrunt hcl validate --working-dir "+rootPath)
728728
require.Error(t, err, "terragrunt hcl validate should return a non-zero exit code on HCL errors")
729729

730-
// As an additional check, we can verify that the error message indicates HCL validation errors.
731-
// This makes the test more robust.
732-
assert.Contains(t, err.Error(), "HCL validation error(s) found")
730+
// As an additional check, verify that the error message indicates HCL validation errors.
731+
// Accept either wording depending on the underlying evaluator.
732+
errMsg := err.Error()
733+
assert.True(t,
734+
strings.Contains(errMsg, "HCL validation error(s) found") || strings.Contains(errMsg, "invalid expression"),
735+
"error should mention HCL validation issues: got %q", errMsg,
736+
)
733737
}
734738

735739
func TestHclvalidateInvalidConfigPath(t *testing.T) {

0 commit comments

Comments
 (0)