diff --git a/cli/flags/shared/shared.go b/cli/flags/shared/shared.go index c3e0024d92..d5a48a04d1 100644 --- a/cli/flags/shared/shared.go +++ b/cli/flags/shared/shared.go @@ -7,6 +7,7 @@ package shared import ( "github.com/gruntwork-io/terragrunt/cli/flags" "github.com/gruntwork-io/terragrunt/internal/cli" + "github.com/gruntwork-io/terragrunt/internal/strict/controls" "github.com/gruntwork-io/terragrunt/options" ) @@ -112,6 +113,13 @@ func NewQueueFlags(opts *options.TerragruntOptions, prefix flags.Prefix) cli.Fla EnvVars: tgPrefix.EnvVars(QueueExcludeExternalFlagName), Destination: &opts.IgnoreExternalDependencies, Usage: "Ignore external dependencies for --all commands.", + Hidden: true, + Action: func(ctx *cli.Context, value bool) error { + if value { + return opts.StrictControls.FilterByNames(controls.QueueExcludeExternal).Evaluate(ctx.Context) + } + return nil + }, }, flags.WithDeprecatedEnvVars(terragruntPrefix.EnvVars("ignore-external-dependencies"), terragruntPrefixControl), ), diff --git a/docs-starlight/src/content/docs/04-reference/03-strict-controls.mdx b/docs-starlight/src/content/docs/04-reference/03-strict-controls.mdx index 393476bd67..f78d545bd5 100644 --- a/docs-starlight/src/content/docs/04-reference/03-strict-controls.mdx +++ b/docs-starlight/src/content/docs/04-reference/03-strict-controls.mdx @@ -193,6 +193,19 @@ and unit-b. The `**` double wildcard indicates any character including `/`, this allows matching multiple directory depths e.g. `units/**` matches unit-a, unit-b, and unit-c. +### queue-exclude-external + +Throw an error when using the deprecated `--queue-exclude-external` flag. + +**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. + +**Example**: +```bash +# This will show a warning (or error with strict control enabled) +$ terragrunt run --all plan --queue-exclude-external +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. +``` + ## Control Categories 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. **Controls**: - [terragrunt-prefix-flags](#terragrunt-prefix-flags) +- [queue-exclude-external](#queue-exclude-external) ### deprecated-env-vars diff --git a/docs-starlight/src/data/flags/queue-exclude-external.mdx b/docs-starlight/src/data/flags/queue-exclude-external.mdx index b250d624d9..709d4adc14 100644 --- a/docs-starlight/src/data/flags/queue-exclude-external.mdx +++ b/docs-starlight/src/data/flags/queue-exclude-external.mdx @@ -6,8 +6,14 @@ env: - TG_QUEUE_EXCLUDE_EXTERNAL --- -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). +import { Aside } from '@astrojs/starlight/components'; -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). + -This flag is useful when you want to limit the scope of execution to only units within your current working directory structure. +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). + +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). diff --git a/docs-starlight/src/data/flags/queue-include-external.mdx b/docs-starlight/src/data/flags/queue-include-external.mdx index 7e797cd006..56a59d2b13 100644 --- a/docs-starlight/src/data/flags/queue-include-external.mdx +++ b/docs-starlight/src/data/flags/queue-include-external.mdx @@ -6,4 +6,8 @@ env: - TG_QUEUE_INCLUDE_EXTERNAL --- -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). +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). + +When this flag is enabled, Terragrunt will include those external dependencies in the queue. + +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. diff --git a/internal/discovery/dependencydiscovery.go b/internal/discovery/dependencydiscovery.go index 43ec1cb72a..09d96bf094 100644 --- a/internal/discovery/dependencydiscovery.go +++ b/internal/discovery/dependencydiscovery.go @@ -8,8 +8,10 @@ import ( "github.com/gruntwork-io/terragrunt/config/hclparse" "github.com/gruntwork-io/terragrunt/internal/component" "github.com/gruntwork-io/terragrunt/internal/errors" + "github.com/gruntwork-io/terragrunt/internal/report" "github.com/gruntwork-io/terragrunt/options" "github.com/gruntwork-io/terragrunt/pkg/log" + "github.com/gruntwork-io/terragrunt/util" "golang.org/x/sync/errgroup" ) @@ -18,6 +20,7 @@ type DependencyDiscovery struct { discoveryContext *component.DiscoveryContext components *component.ThreadSafeComponents externalDependencies *component.ThreadSafeComponents + report *report.Report mu *sync.RWMutex seenComponents map[string]struct{} parserOptions []hclparse.Option @@ -76,6 +79,13 @@ func (dd *DependencyDiscovery) WithDiscoveryContext(discoveryContext *component. return dd } +// WithReport sets the report for recording excluded external dependencies. +func (dd *DependencyDiscovery) WithReport(r *report.Report) *DependencyDiscovery { + dd.report = r + + return dd +} + func (dd *DependencyDiscovery) Discover( ctx context.Context, l log.Logger, @@ -196,7 +206,7 @@ func (dd *DependencyDiscovery) discoverDependencies( for _, depPath := range depPaths { g.Go(func() error { - depComponent := dd.dependencyToDiscover(dComponent, depPath) + depComponent := dd.dependencyToDiscover(l, dComponent, depPath) if depComponent == nil { return nil } @@ -231,6 +241,7 @@ func (dd *DependencyDiscovery) discoverDependencies( // marking as external if it's outside the working directory of discovery, and linking dependencies. // Returns nil if the dependency shouldn't be involved in discovery any further (e.g., already processed or ignored). func (dd *DependencyDiscovery) dependencyToDiscover( + l log.Logger, dComponent component.Component, depPath string, ) component.Component { @@ -274,6 +285,15 @@ func (dd *DependencyDiscovery) dependencyToDiscover( existingDep, _ = dd.externalDependencies.EnsureComponent(depComponent) dComponent.AddDependency(existingDep) + l.Debugf("Excluded external dependency: %s", depComponent.DisplayPath()) + + // Record in report as excluded external dependency + if dd.report != nil { + absPath := util.CleanPath(depPath) + run, _ := dd.report.EnsureRun(absPath) + _ = dd.report.EndRun(run.Path, report.WithResult(report.ResultExcluded), report.WithReason(report.ReasonExcludeExternal)) + } + dd.markSeen(depPath) return nil diff --git a/internal/discovery/discovery.go b/internal/discovery/discovery.go index f8db6d7027..2354302fff 100644 --- a/internal/discovery/discovery.go +++ b/internal/discovery/discovery.go @@ -17,6 +17,7 @@ import ( "github.com/gruntwork-io/terragrunt/internal/component" "github.com/gruntwork-io/terragrunt/internal/experiment" "github.com/gruntwork-io/terragrunt/internal/filter" + "github.com/gruntwork-io/terragrunt/internal/report" "github.com/gruntwork-io/terragrunt/shell" "github.com/gruntwork-io/terragrunt/util" @@ -110,6 +111,9 @@ type Discovery struct { // gitExpressions contains Git filter expressions that require worktree discovery gitExpressions filter.GitExpressions + // report is used for recording excluded external dependencies during discovery. + report *report.Report + // compiledIncludePatterns are precompiled glob patterns for includeDirs. compiledIncludePatterns []CompiledPattern @@ -253,6 +257,13 @@ func (d *Discovery) WithDiscoverExternalDependencies() *Discovery { return d } +// WithReport sets the report for recording excluded external dependencies. +func (d *Discovery) WithReport(r *report.Report) *Discovery { + d.report = r + + return d +} + // WithSuppressParseErrors sets the SuppressParseErrors flag to true. func (d *Discovery) WithSuppressParseErrors() *Discovery { d.suppressParseErrors = true @@ -1158,6 +1169,11 @@ func (d *Discovery) Discover( dependencyDiscovery = dependencyDiscovery.WithParserOptions(d.parserOptions) } + // pass report for recording excluded external dependencies + if d.report != nil { + dependencyDiscovery = dependencyDiscovery.WithReport(d.report) + } + discoveryErr := dependencyDiscovery.Discover(discoveryCtx, l, opts, dependencyStartingComponents) if discoveryErr != nil { if !d.suppressParseErrors { diff --git a/internal/runner/common/options.go b/internal/runner/common/options.go index d0b0abebd4..7c6c01832e 100644 --- a/internal/runner/common/options.go +++ b/internal/runner/common/options.go @@ -47,13 +47,27 @@ func WithParseOptions(parserOptions []hclparse.Option) Option { } } +// ReportProvider exposes the report attached to an Option. +type ReportProvider interface { + GetReport() *report.Report +} + +// reportOption wraps a report and implements both Option and ReportProvider. +type reportOption struct { + report *report.Report +} + +func (o reportOption) Apply(stack StackRunner) { + stack.SetReport(o.report) +} + +func (o reportOption) GetReport() *report.Report { + return o.report +} + // WithReport attaches a report collector to the stack, enabling run summaries and metrics. func WithReport(r *report.Report) Option { - return optionImpl{ - apply: func(stack StackRunner) { - stack.SetReport(r) - }, - } + return reportOption{report: r} } // WorktreeOption carries worktrees through the runner pipeline for git filter expressions. diff --git a/internal/runner/runnerpool/builder.go b/internal/runner/runnerpool/builder.go index 47d9aa2655..978902f32c 100644 --- a/internal/runner/runnerpool/builder.go +++ b/internal/runner/runnerpool/builder.go @@ -21,6 +21,5 @@ func Build( return nil, err } - // Create the runner return createRunner(ctx, l, terragruntOptions, discovered, opts...) } diff --git a/internal/runner/runnerpool/builder_helpers.go b/internal/runner/runnerpool/builder_helpers.go index 8acbaf815e..bd9e782492 100644 --- a/internal/runner/runnerpool/builder_helpers.go +++ b/internal/runner/runnerpool/builder_helpers.go @@ -8,6 +8,7 @@ import ( "github.com/gruntwork-io/terragrunt/internal/discovery" "github.com/gruntwork-io/terragrunt/internal/errors" "github.com/gruntwork-io/terragrunt/internal/filter" + "github.com/gruntwork-io/terragrunt/internal/report" "github.com/gruntwork-io/terragrunt/internal/runner/common" "github.com/gruntwork-io/terragrunt/internal/worktrees" "github.com/gruntwork-io/terragrunt/options" @@ -76,6 +77,17 @@ func extractWorktrees(opts []common.Option) *worktrees.Worktrees { return nil } +// extractReport finds ReportProvider in options and returns the report. +func extractReport(opts []common.Option) *report.Report { + for _, opt := range opts { + if rp, ok := opt.(common.ReportProvider); ok { + return rp.GetReport() + } + } + + return nil +} + // newBaseDiscovery constructs the base discovery with common immutable options. func newBaseDiscovery( tgOpts *options.TerragruntOptions, @@ -88,10 +100,9 @@ func newBaseDiscovery( anyOpts[i] = v } - return discovery. + d := discovery. NewDiscovery(workingDir). WithOptions(anyOpts...). - WithDiscoverExternalDependencies(). WithParseInclude(). WithParseExclude(). WithDiscoverDependencies(). @@ -102,6 +113,17 @@ func newBaseDiscovery( Cmd: tgOpts.TerraformCliArgs.First(), Args: tgOpts.TerraformCliArgs.Tail(), }) + + // Only include external dependencies in the run queue if explicitly requested via --queue-include-external. + // This restores the pre-v0.94.0 behavior where external dependencies were excluded by default. + // External dependencies are still detected and tracked for reporting purposes, but not fully discovered + // unless this flag is set. + // See: https://github.com/gruntwork-io/terragrunt/issues/5195 + if tgOpts.IncludeExternalDependencies { + d = d.WithDiscoverExternalDependencies() + } + + return d } // prepareDiscovery constructs a configured discovery instance based on Terragrunt options and flags. @@ -153,6 +175,11 @@ func prepareDiscovery( d = d.WithWorktrees(w) } + // Apply report for recording excluded external dependencies + if r := extractReport(opts); r != nil { + d = d.WithReport(r) + } + return d, nil } diff --git a/internal/runner/runnerpool/runner.go b/internal/runner/runnerpool/runner.go index 593d1fe743..701ccff90f 100644 --- a/internal/runner/runnerpool/runner.go +++ b/internal/runner/runnerpool/runner.go @@ -189,16 +189,6 @@ func resolveUnitsFromDiscovery( AssumeAlreadyApplied: false, } - // Handle external units - check if they should be excluded - if unit.External() { - if stack.Execution != nil && stack.Execution.TerragruntOptions != nil && - stack.Execution.TerragruntOptions.IgnoreExternalDependencies { - unit.Execution.AssumeAlreadyApplied = true - unit.Execution.FlagExcluded = true - l.Infof("Excluded external dependency: %s", unit.DisplayPath()) - } - } - // Store config from discovery context if available if unit.DiscoveryContext() != nil && unit.Config() == nil { // Config should already be set during discovery @@ -909,6 +899,10 @@ func FilterDiscoveredUnits(discovered component.Components, units []*component.U // WithOptions updates the stack with the provided options. func (r *Runner) WithOptions(opts ...common.Option) *Runner { for _, opt := range opts { + if opt == nil { + continue + } + opt.Apply(r) } diff --git a/internal/runner/runnerpool/runner_test.go b/internal/runner/runnerpool/runner_test.go index 8066178f2b..dbab4a0812 100644 --- a/internal/runner/runnerpool/runner_test.go +++ b/internal/runner/runnerpool/runner_test.go @@ -35,7 +35,7 @@ func TestDiscoveryResolverMatchesLegacyPaths(t *testing.T) { l := thlogger.CreateLogger() - runner, err := runnerpool.NewRunnerPoolStack(context.Background(), l, opts, discovered) + runner, err := runnerpool.NewRunnerPoolStack(context.Background(), l, opts, discovered, nil) require.NoError(t, err) units := runner.GetStack().Units diff --git a/internal/strict/controls/controls.go b/internal/strict/controls/controls.go index 955d5f861f..3d6e03caf3 100644 --- a/internal/strict/controls/controls.go +++ b/internal/strict/controls/controls.go @@ -55,6 +55,9 @@ const ( // DoubleStar enables the use of the `**` glob pattern as a way to match files in subdirectories. // and will log a warning when using **/* DoubleStar = "double-star" + + // QueueExcludeExternal is the control that prevents the use of the deprecated `--queue-exclude-external` flag. + QueueExcludeExternal = "queue-exclude-external" ) //nolint:lll @@ -209,6 +212,13 @@ func New() strict.Controls { Error: errors.New("Using `**` to select all files in a directory and its subdirectories is enabled. **/* now matches subdirectories with at least a depth of one."), Warning: "Using `**` to select all files in a directory and its subdirectories is enabled. **/* now matches subdirectories with at least a depth of one.", }, + &Control{ + Name: QueueExcludeExternal, + Description: "Prevents the use of the deprecated `--queue-exclude-external` flag. External dependencies are now excluded by default.", + Category: stageCategory, + Error: errors.New("The `--queue-exclude-external` flag is no longer supported. External dependencies are now excluded by default. Use --queue-include-external to include them."), + Warning: "The `--queue-exclude-external` flag is deprecated and will be removed in a future version of Terragrunt. External dependencies are now excluded by default.", + }, } return controls.Sort() diff --git a/test/fixtures/regressions/5195-scope-escape/bastion/main.tf b/test/fixtures/regressions/5195-scope-escape/bastion/main.tf new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/regressions/5195-scope-escape/bastion/terragrunt.hcl b/test/fixtures/regressions/5195-scope-escape/bastion/terragrunt.hcl new file mode 100644 index 0000000000..8b22181778 --- /dev/null +++ b/test/fixtures/regressions/5195-scope-escape/bastion/terragrunt.hcl @@ -0,0 +1,9 @@ +dependency "shared" { + config_path = "../module2" + + # Mock outputs to avoid needing actual terraform state + mock_outputs = { + output_value = "mock" + } + mock_outputs_allowed_terraform_commands = ["plan", "destroy"] +} diff --git a/test/fixtures/regressions/5195-scope-escape/module1/main.tf b/test/fixtures/regressions/5195-scope-escape/module1/main.tf new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/regressions/5195-scope-escape/module1/terragrunt.hcl b/test/fixtures/regressions/5195-scope-escape/module1/terragrunt.hcl new file mode 100644 index 0000000000..c54196babc --- /dev/null +++ b/test/fixtures/regressions/5195-scope-escape/module1/terragrunt.hcl @@ -0,0 +1,4 @@ +# This module depends on bastion - it should NOT be discovered when running from bastion/ +dependency "bastion" { + config_path = "../bastion" +} diff --git a/test/fixtures/regressions/5195-scope-escape/module2/main.tf b/test/fixtures/regressions/5195-scope-escape/module2/main.tf new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/regressions/5195-scope-escape/module2/terragrunt.hcl b/test/fixtures/regressions/5195-scope-escape/module2/terragrunt.hcl new file mode 100644 index 0000000000..49e6894b18 --- /dev/null +++ b/test/fixtures/regressions/5195-scope-escape/module2/terragrunt.hcl @@ -0,0 +1 @@ +# Simple unit with no dependencies - should NOT be discovered when running from bastion/ diff --git a/test/integration_dag_test.go b/test/integration_dag_test.go index c7fbfa9495..5e525d3f0e 100644 --- a/test/integration_dag_test.go +++ b/test/integration_dag_test.go @@ -19,7 +19,6 @@ func TestDagGraphFlagsRegistration(t *testing.T) { assert.Empty(t, stderr) assert.Contains(t, stdout, "--queue-exclude-dir", "queue-exclude-dir flag should be present") - assert.Contains(t, stdout, "--queue-exclude-external", "queue-exclude-external flag should be present") assert.Contains(t, stdout, "--queue-excludes-file", "queue-excludes-file flag should be present") assert.Contains(t, stdout, "--queue-ignore-dag-order", "queue-ignore-dag-order flag should be present") assert.Contains(t, stdout, "--queue-ignore-errors", "queue-ignore-errors flag should be present") diff --git a/test/integration_regressions_test.go b/test/integration_regressions_test.go index 6964bc9c42..0361315155 100644 --- a/test/integration_regressions_test.go +++ b/test/integration_regressions_test.go @@ -26,6 +26,7 @@ const ( testFixtureParsingDeprecated = "fixtures/parsing/exposed-include-with-deprecated-inputs" testFixtureSensitiveValues = "fixtures/regressions/sensitive-values" testFixtureStackDetection = "fixtures/regressions/multiple-stacks" + testFixtureScopeEscape = "fixtures/regressions/5195-scope-escape" ) func TestNoAutoInit(t *testing.T) { @@ -317,6 +318,8 @@ func TestRunAllWithGenerateAndExpose(t *testing.T) { // TestRunAllWithGenerateAndExpose_WithProviderCacheAndExcludeExternal mirrors the user repro flags // to ensure no cryptic errors or formatting artifacts appear in logs when using provider cache and // excluding external dependencies. +// Note: As of #5195 fix, external dependencies are excluded by default, so --queue-exclude-external +// is now deprecated and acts as a no-op. This test verifies the behavior still works correctly. func TestRunAllWithGenerateAndExpose_WithProviderCacheAndExcludeExternal(t *testing.T) { t.Parallel() @@ -325,7 +328,8 @@ func TestRunAllWithGenerateAndExpose_WithProviderCacheAndExcludeExternal(t *test tmpEnvPath := helpers.CopyEnvironment(t, testFixture) rootPath := util.JoinPath(tmpEnvPath, testFixture, "services-info") - // Set TG_PROVIDER_CACHE=1 and use --queue-exclude-external as in the repro steps + // Use --queue-exclude-external as in the repro steps (now deprecated, acts as no-op since + // external dependencies are excluded by default after #5195 fix) stdout, stderr, err := helpers.RunTerragruntCommandWithOutput( t, "terragrunt run --all --queue-exclude-external plan --non-interactive --working-dir "+rootPath, @@ -565,3 +569,100 @@ func TestOutputFlushOnInterrupt(t *testing.T) { require.Greater(t, totalWrites, writesBeforeCancel, "Additional writes should occur after cancellation (flush writes)") require.NotEmpty(t, output, "Expected output to be flushed after cancellation") } + +// TestRunAllDoesNotIncludeExternalDepsInQueue tests that running `terragrunt run --all` from a subdirectory +// does NOT include external dependencies in the execution queue. +// This is a regression test for issue #5195 where v0.94.0 incorrectly included external dependencies +// in the run queue, causing dangerous operations like destroy to execute against unintended modules. +// +// The test structure is: +// +// fixtures/regressions/5195-scope-escape/ +// ├── bastion/ <- Run from here, has dependency on module2 +// │ ├── terragrunt.hcl (depends on ../module2 with mock_outputs) +// │ └── main.tf +// ├── module1/ <- Has dependency on bastion +// │ ├── terragrunt.hcl +// │ └── main.tf +// └── module2/ <- External dependency of bastion +// ├── terragrunt.hcl +// └── main.tf +// +// Expected behavior (v0.93.13 and after fix): +// - External dependency (module2) is discovered but EXCLUDED from execution +// - Summary shows "Excluded 1" for the external dep +// - Only bastion (Unit .) is actually executed +// +// Bug behavior (v0.94.0): +// - External dependency is included and EXECUTED (not excluded) +// - This causes unintended operations on external modules +// +// See: https://github.com/gruntwork-io/terragrunt/issues/5195 +func TestRunAllDoesNotIncludeExternalDepsInQueue(t *testing.T) { + t.Parallel() + + helpers.CleanupTerraformFolder(t, testFixtureScopeEscape) + tmpEnvPath := helpers.CopyEnvironment(t, testFixtureScopeEscape) + rootPath := util.JoinPath(tmpEnvPath, testFixtureScopeEscape) + bastionPath := util.JoinPath(rootPath, "bastion") + + // Initialize git repo - this is important because discovery uses git root for scope + helpers.CreateGitRepo(t, rootPath) + + // Run terragrunt run --all plan from the bastion directory + stdout, stderr, err := helpers.RunTerragruntCommandWithOutput( + t, + "terragrunt run --all plan --log-level debug --non-interactive --working-dir "+bastionPath, + ) + + // The command should succeed + require.NoError(t, err) + + // External dependencies should be excluded (not executed) + // The log should show they were excluded during discovery + assert.Contains(t, stderr, "Excluded external dependency", + "External dependencies should be logged as excluded") + + // Should see bastion (displayed as "." since it's the current directory) + assert.Contains(t, stderr, "Unit .", + "Should discover the current directory (bastion) as '.'") + + // Report shows 2 units (bastion + excluded external dep) + assert.Contains(t, stdout, "2 units", + "Should have 2 units total (bastion + excluded external dep)") + assert.Contains(t, stdout, "Succeeded 1", + "Only bastion should succeed") + assert.Contains(t, stdout, "Excluded 1", + "External dependency should be excluded in report") +} + +// TestRunAllFromParentDiscoversAllModules verifies that running from the parent directory +// correctly discovers all modules in the hierarchy. This is the control test for +// TestRunAllDoesNotEscapeWorkingDir. +func TestRunAllFromParentDiscoversAllModules(t *testing.T) { + t.Parallel() + + helpers.CleanupTerraformFolder(t, testFixtureScopeEscape) + tmpEnvPath := helpers.CopyEnvironment(t, testFixtureScopeEscape) + rootPath := util.JoinPath(tmpEnvPath, testFixtureScopeEscape) + + // Initialize git repo - this is important because discovery uses git root for scope + helpers.CreateGitRepo(t, rootPath) + + // Run terragrunt run --all plan from the parent directory (live/) + _, stderr, err := helpers.RunTerragruntCommandWithOutput( + t, + "terragrunt run --all plan --non-interactive --working-dir "+rootPath, + ) + + // The command should succeed in terms of discovery + _ = err + + // Should see all three modules when running from parent directory + assert.Contains(t, stderr, "bastion", + "Should discover bastion when running from parent directory") + assert.Contains(t, stderr, "module1", + "Should discover module1 when running from parent directory") + assert.Contains(t, stderr, "module2", + "Should discover module2 when running from parent directory") +} diff --git a/test/integration_report_test.go b/test/integration_report_test.go index 676184c1f9..b645af8377 100644 --- a/test/integration_report_test.go +++ b/test/integration_report_test.go @@ -512,7 +512,7 @@ func TestReportWithExternalDependenciesExcluded(t *testing.T) { stdout, stderr, err := helpers.RunTerragruntCommandWithOutput( t, fmt.Sprintf( - "terragrunt run --all plan --queue-exclude-external --feature dep=%s --working-dir %s --report-file %s", + "terragrunt run --all plan --queue-exclude-external --log-level debug --feature dep=%s --working-dir %s --report-file %s", dep, rootPath, reportFile, @@ -526,6 +526,10 @@ func TestReportWithExternalDependenciesExcluded(t *testing.T) { assert.NotContains(t, stderr, "run not found in report") assert.Contains(t, stdout, "Run Summary") + // External dependencies should be logged as excluded during discovery + assert.Contains(t, stderr, "Excluded external dependency", + "External dependencies should be logged as excluded") + // Verify that the report file exists assert.FileExists(t, reportFile)