Skip to content

Commit d24e231

Browse files
authored
bug: runner pool external dependencies inclusion fix (#5199)
* bug: runner pool external depednencies inclusion fix * chore: discovery simplification * discovery simplification * chore: simplify reporting * chore: external dependencies update * chore: PR cleanup * chore: strict controls update * chore: failing tests fixes * chore: failing tests fixes * chore: docs simplification
1 parent 6e2c04b commit d24e231

21 files changed

Lines changed: 257 additions & 27 deletions

File tree

cli/flags/shared/shared.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package shared
77
import (
88
"github.com/gruntwork-io/terragrunt/cli/flags"
99
"github.com/gruntwork-io/terragrunt/internal/cli"
10+
"github.com/gruntwork-io/terragrunt/internal/strict/controls"
1011
"github.com/gruntwork-io/terragrunt/options"
1112
)
1213

@@ -112,6 +113,13 @@ func NewQueueFlags(opts *options.TerragruntOptions, prefix flags.Prefix) cli.Fla
112113
EnvVars: tgPrefix.EnvVars(QueueExcludeExternalFlagName),
113114
Destination: &opts.IgnoreExternalDependencies,
114115
Usage: "Ignore external dependencies for --all commands.",
116+
Hidden: true,
117+
Action: func(ctx *cli.Context, value bool) error {
118+
if value {
119+
return opts.StrictControls.FilterByNames(controls.QueueExcludeExternal).Evaluate(ctx.Context)
120+
}
121+
return nil
122+
},
115123
},
116124
flags.WithDeprecatedEnvVars(terragruntPrefix.EnvVars("ignore-external-dependencies"), terragruntPrefixControl),
117125
),

docs-starlight/src/content/docs/04-reference/03-strict-controls.mdx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,19 @@ and unit-b.
193193
The `**` double wildcard indicates any character including `/`, this allows
194194
matching multiple directory depths e.g. `units/**` matches unit-a, unit-b, and unit-c.
195195

196+
### queue-exclude-external
197+
198+
Throw an error when using the deprecated `--queue-exclude-external` flag.
199+
200+
**Reason**: External dependencies are now excluded by default. The `--queue-exclude-external` flag is no longer needed and has been deprecated. Use `--queue-include-external` if you need to include external dependencies.
201+
202+
**Example**:
203+
```bash
204+
# This will show a warning (or error with strict control enabled)
205+
$ terragrunt run --all plan --queue-exclude-external
206+
WARN The `--queue-exclude-external` flag is deprecated and will be removed in a future version of Terragrunt. External dependencies are now excluded by default.
207+
```
208+
196209
## Control Categories
197210

198211
Certain strict controls are grouped into categories to make it easier to enable multiple strict controls at once.
@@ -217,6 +230,7 @@ Throw an error when using the deprecated flags.
217230
**Controls**:
218231

219232
- [terragrunt-prefix-flags](#terragrunt-prefix-flags)
233+
- [queue-exclude-external](#queue-exclude-external)
220234

221235
### deprecated-env-vars
222236

docs-starlight/src/data/flags/queue-exclude-external.mdx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@ env:
66
- TG_QUEUE_EXCLUDE_EXTERNAL
77
---
88

9-
When enabled, Terragrunt will exclude external dependencies (dependencies outside the current working directory) from the queue when running commands with [`all`](/docs/reference/cli/commands/run#all).
9+
import { Aside } from '@astrojs/starlight/components';
1010

11-
Note that an external dependency is a dependency that is outside the current Terragrunt working directory and is not within any directories specified by [`queue-include-dir`](/docs/reference/cli/commands/run#queue-include-dir).
11+
<Aside type="caution" title="Deprecated">
12+
This flag is deprecated and will be removed in a future version of Terragrunt. External dependencies are now excluded by default, making this flag unnecessary.
13+
14+
Use [`--queue-include-external`](/docs/reference/cli/commands/run#queue-include-external) if you need to include external dependencies.
15+
</Aside>
1216

13-
This flag is useful when you want to limit the scope of execution to only units within your current working directory structure.
17+
When enabled, Terragrunt will exclude external dependencies (dependencies outside the current working directory) from the queue when running commands with [`--all`](/docs/reference/cli/commands/run#all).
18+
19+
Note that an external dependency is a dependency that is outside the current Terragrunt working directory and is not within any directories specified by [`queue-include-dir`](/docs/reference/cli/commands/run#queue-include-dir).

docs-starlight/src/data/flags/queue-include-external.mdx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,8 @@ env:
66
- TG_QUEUE_INCLUDE_EXTERNAL
77
---
88

9-
When enabled, Terragrunt will include external dependencies (dependencies discovered outside the current working directory) in the queue when running commands with [`--all`](/docs/reference/cli/commands/run#all) or [`--graph`](/docs/reference/cli/commands/run#graph).
9+
By default, Terragrunt excludes external dependencies (dependencies discovered outside the current working directory) from the queue when running commands with [`--all`](/docs/reference/cli/commands/run#all) or [`--graph`](/docs/reference/cli/commands/run#graph).
10+
11+
When this flag is enabled, Terragrunt will include those external dependencies in the queue.
12+
13+
This flag is useful when you have units that depend on infrastructure outside your current working directory and need those dependencies to be processed as part of the run.

internal/discovery/dependencydiscovery.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ import (
88
"github.com/gruntwork-io/terragrunt/config/hclparse"
99
"github.com/gruntwork-io/terragrunt/internal/component"
1010
"github.com/gruntwork-io/terragrunt/internal/errors"
11+
"github.com/gruntwork-io/terragrunt/internal/report"
1112
"github.com/gruntwork-io/terragrunt/options"
1213
"github.com/gruntwork-io/terragrunt/pkg/log"
14+
"github.com/gruntwork-io/terragrunt/util"
1315
"golang.org/x/sync/errgroup"
1416
)
1517

@@ -18,6 +20,7 @@ type DependencyDiscovery struct {
1820
discoveryContext *component.DiscoveryContext
1921
components *component.ThreadSafeComponents
2022
externalDependencies *component.ThreadSafeComponents
23+
report *report.Report
2124
mu *sync.RWMutex
2225
seenComponents map[string]struct{}
2326
parserOptions []hclparse.Option
@@ -76,6 +79,13 @@ func (dd *DependencyDiscovery) WithDiscoveryContext(discoveryContext *component.
7679
return dd
7780
}
7881

82+
// WithReport sets the report for recording excluded external dependencies.
83+
func (dd *DependencyDiscovery) WithReport(r *report.Report) *DependencyDiscovery {
84+
dd.report = r
85+
86+
return dd
87+
}
88+
7989
func (dd *DependencyDiscovery) Discover(
8090
ctx context.Context,
8191
l log.Logger,
@@ -196,7 +206,7 @@ func (dd *DependencyDiscovery) discoverDependencies(
196206

197207
for _, depPath := range depPaths {
198208
g.Go(func() error {
199-
depComponent := dd.dependencyToDiscover(dComponent, depPath)
209+
depComponent := dd.dependencyToDiscover(l, dComponent, depPath)
200210
if depComponent == nil {
201211
return nil
202212
}
@@ -231,6 +241,7 @@ func (dd *DependencyDiscovery) discoverDependencies(
231241
// marking as external if it's outside the working directory of discovery, and linking dependencies.
232242
// Returns nil if the dependency shouldn't be involved in discovery any further (e.g., already processed or ignored).
233243
func (dd *DependencyDiscovery) dependencyToDiscover(
244+
l log.Logger,
234245
dComponent component.Component,
235246
depPath string,
236247
) component.Component {
@@ -274,6 +285,15 @@ func (dd *DependencyDiscovery) dependencyToDiscover(
274285
existingDep, _ = dd.externalDependencies.EnsureComponent(depComponent)
275286
dComponent.AddDependency(existingDep)
276287

288+
l.Debugf("Excluded external dependency: %s", depComponent.DisplayPath())
289+
290+
// Record in report as excluded external dependency
291+
if dd.report != nil {
292+
absPath := util.CleanPath(depPath)
293+
run, _ := dd.report.EnsureRun(absPath)
294+
_ = dd.report.EndRun(run.Path, report.WithResult(report.ResultExcluded), report.WithReason(report.ReasonExcludeExternal))
295+
}
296+
277297
dd.markSeen(depPath)
278298

279299
return nil

internal/discovery/discovery.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/gruntwork-io/terragrunt/internal/component"
1818
"github.com/gruntwork-io/terragrunt/internal/experiment"
1919
"github.com/gruntwork-io/terragrunt/internal/filter"
20+
"github.com/gruntwork-io/terragrunt/internal/report"
2021
"github.com/gruntwork-io/terragrunt/shell"
2122
"github.com/gruntwork-io/terragrunt/util"
2223

@@ -110,6 +111,9 @@ type Discovery struct {
110111
// gitExpressions contains Git filter expressions that require worktree discovery
111112
gitExpressions filter.GitExpressions
112113

114+
// report is used for recording excluded external dependencies during discovery.
115+
report *report.Report
116+
113117
// compiledIncludePatterns are precompiled glob patterns for includeDirs.
114118
compiledIncludePatterns []CompiledPattern
115119

@@ -253,6 +257,13 @@ func (d *Discovery) WithDiscoverExternalDependencies() *Discovery {
253257
return d
254258
}
255259

260+
// WithReport sets the report for recording excluded external dependencies.
261+
func (d *Discovery) WithReport(r *report.Report) *Discovery {
262+
d.report = r
263+
264+
return d
265+
}
266+
256267
// WithSuppressParseErrors sets the SuppressParseErrors flag to true.
257268
func (d *Discovery) WithSuppressParseErrors() *Discovery {
258269
d.suppressParseErrors = true
@@ -1158,6 +1169,11 @@ func (d *Discovery) Discover(
11581169
dependencyDiscovery = dependencyDiscovery.WithParserOptions(d.parserOptions)
11591170
}
11601171

1172+
// pass report for recording excluded external dependencies
1173+
if d.report != nil {
1174+
dependencyDiscovery = dependencyDiscovery.WithReport(d.report)
1175+
}
1176+
11611177
discoveryErr := dependencyDiscovery.Discover(discoveryCtx, l, opts, dependencyStartingComponents)
11621178
if discoveryErr != nil {
11631179
if !d.suppressParseErrors {

internal/runner/common/options.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,27 @@ func WithParseOptions(parserOptions []hclparse.Option) Option {
4747
}
4848
}
4949

50+
// ReportProvider exposes the report attached to an Option.
51+
type ReportProvider interface {
52+
GetReport() *report.Report
53+
}
54+
55+
// reportOption wraps a report and implements both Option and ReportProvider.
56+
type reportOption struct {
57+
report *report.Report
58+
}
59+
60+
func (o reportOption) Apply(stack StackRunner) {
61+
stack.SetReport(o.report)
62+
}
63+
64+
func (o reportOption) GetReport() *report.Report {
65+
return o.report
66+
}
67+
5068
// WithReport attaches a report collector to the stack, enabling run summaries and metrics.
5169
func WithReport(r *report.Report) Option {
52-
return optionImpl{
53-
apply: func(stack StackRunner) {
54-
stack.SetReport(r)
55-
},
56-
}
70+
return reportOption{report: r}
5771
}
5872

5973
// WorktreeOption carries worktrees through the runner pipeline for git filter expressions.

internal/runner/runnerpool/builder.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,5 @@ func Build(
2121
return nil, err
2222
}
2323

24-
// Create the runner
2524
return createRunner(ctx, l, terragruntOptions, discovered, opts...)
2625
}

internal/runner/runnerpool/builder_helpers.go

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/gruntwork-io/terragrunt/internal/discovery"
99
"github.com/gruntwork-io/terragrunt/internal/errors"
1010
"github.com/gruntwork-io/terragrunt/internal/filter"
11+
"github.com/gruntwork-io/terragrunt/internal/report"
1112
"github.com/gruntwork-io/terragrunt/internal/runner/common"
1213
"github.com/gruntwork-io/terragrunt/internal/worktrees"
1314
"github.com/gruntwork-io/terragrunt/options"
@@ -76,6 +77,17 @@ func extractWorktrees(opts []common.Option) *worktrees.Worktrees {
7677
return nil
7778
}
7879

80+
// extractReport finds ReportProvider in options and returns the report.
81+
func extractReport(opts []common.Option) *report.Report {
82+
for _, opt := range opts {
83+
if rp, ok := opt.(common.ReportProvider); ok {
84+
return rp.GetReport()
85+
}
86+
}
87+
88+
return nil
89+
}
90+
7991
// newBaseDiscovery constructs the base discovery with common immutable options.
8092
func newBaseDiscovery(
8193
tgOpts *options.TerragruntOptions,
@@ -88,10 +100,9 @@ func newBaseDiscovery(
88100
anyOpts[i] = v
89101
}
90102

91-
return discovery.
103+
d := discovery.
92104
NewDiscovery(workingDir).
93105
WithOptions(anyOpts...).
94-
WithDiscoverExternalDependencies().
95106
WithParseInclude().
96107
WithParseExclude().
97108
WithDiscoverDependencies().
@@ -102,6 +113,17 @@ func newBaseDiscovery(
102113
Cmd: tgOpts.TerraformCliArgs.First(),
103114
Args: tgOpts.TerraformCliArgs.Tail(),
104115
})
116+
117+
// Only include external dependencies in the run queue if explicitly requested via --queue-include-external.
118+
// This restores the pre-v0.94.0 behavior where external dependencies were excluded by default.
119+
// External dependencies are still detected and tracked for reporting purposes, but not fully discovered
120+
// unless this flag is set.
121+
// See: https://github.com/gruntwork-io/terragrunt/issues/5195
122+
if tgOpts.IncludeExternalDependencies {
123+
d = d.WithDiscoverExternalDependencies()
124+
}
125+
126+
return d
105127
}
106128

107129
// prepareDiscovery constructs a configured discovery instance based on Terragrunt options and flags.
@@ -153,6 +175,11 @@ func prepareDiscovery(
153175
d = d.WithWorktrees(w)
154176
}
155177

178+
// Apply report for recording excluded external dependencies
179+
if r := extractReport(opts); r != nil {
180+
d = d.WithReport(r)
181+
}
182+
156183
return d, nil
157184
}
158185

internal/runner/runnerpool/runner.go

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -189,16 +189,6 @@ func resolveUnitsFromDiscovery(
189189
AssumeAlreadyApplied: false,
190190
}
191191

192-
// Handle external units - check if they should be excluded
193-
if unit.External() {
194-
if stack.Execution != nil && stack.Execution.TerragruntOptions != nil &&
195-
stack.Execution.TerragruntOptions.IgnoreExternalDependencies {
196-
unit.Execution.AssumeAlreadyApplied = true
197-
unit.Execution.FlagExcluded = true
198-
l.Infof("Excluded external dependency: %s", unit.DisplayPath())
199-
}
200-
}
201-
202192
// Store config from discovery context if available
203193
if unit.DiscoveryContext() != nil && unit.Config() == nil {
204194
// Config should already be set during discovery
@@ -909,6 +899,10 @@ func FilterDiscoveredUnits(discovered component.Components, units []*component.U
909899
// WithOptions updates the stack with the provided options.
910900
func (r *Runner) WithOptions(opts ...common.Option) *Runner {
911901
for _, opt := range opts {
902+
if opt == nil {
903+
continue
904+
}
905+
912906
opt.Apply(r)
913907
}
914908

0 commit comments

Comments
 (0)