Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions internal/cli/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (

"errors"

"github.com/gruntwork-io/terragrunt/internal/venv"
"github.com/gruntwork-io/terragrunt/internal/version"
"github.com/gruntwork-io/terragrunt/pkg/config"

Expand All @@ -45,9 +46,12 @@ type App struct {
l log.Logger
}

// NewApp creates the Terragrunt CLI App.
func NewApp(l log.Logger, opts *options.TerragruntOptions) *App {
terragruntCommands := commands.New(l, opts)
// NewApp creates the Terragrunt CLI App. The supplied [venv.Venv] is the
// root virtualized environment; it is threaded through to the command
// constructors and captured by their Action closures rather than held on
// the App, so virtualized handlers stay function parameters.
func NewApp(l log.Logger, opts *options.TerragruntOptions, v venv.Venv) *App {
terragruntCommands := commands.New(l, opts, v)

app := clihelper.NewApp()
app.Name = AppName
Expand All @@ -57,7 +61,7 @@ func NewApp(l log.Logger, opts *options.TerragruntOptions) *App {
app.Writer = opts.Writers.Writer
app.ErrWriter = opts.Writers.ErrWriter
app.Flags = global.NewFlags(l, opts, nil)
app.Commands = terragruntCommands.WrapAction(commands.WrapWithTelemetry(l, opts))
app.Commands = terragruntCommands.WrapAction(commands.WrapWithTelemetry(l, opts, v))
app.Before = beforeAction(opts)
app.OsExiter = OSExiter
app.ExitErrHandler = ExitErrHandler
Expand Down
17 changes: 9 additions & 8 deletions internal/cli/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/gruntwork-io/terragrunt/internal/clihelper"
"github.com/gruntwork-io/terragrunt/internal/iacargs"
"github.com/gruntwork-io/terragrunt/internal/tf"
"github.com/gruntwork-io/terragrunt/internal/venv"
"github.com/gruntwork-io/terragrunt/pkg/config"
"github.com/gruntwork-io/terragrunt/pkg/log"
"github.com/gruntwork-io/terragrunt/pkg/log/format"
Expand Down Expand Up @@ -739,7 +740,7 @@ func TestTerragruntVersion(t *testing.T) {
for _, tc := range testCases {
output := &bytes.Buffer{}
opts := options.NewTerragruntOptionsWithWriters(output, os.Stderr)
app := cli.NewApp(logger.CreateLogger(), opts)
app := cli.NewApp(logger.CreateLogger(), opts, venv.OSVenv())
app.Version = version

err := app.Run(tc.args)
Expand All @@ -755,7 +756,7 @@ func TestTerragruntHelp(t *testing.T) {
terragruntPrefix := flags.Prefix{flags.TerragruntPrefix}

opts := options.NewTerragruntOptions()
app := cli.NewApp(logger.CreateLogger(), opts)
app := cli.NewApp(logger.CreateLogger(), opts, venv.OSVenv())

testCases := []struct {
expected string
Expand Down Expand Up @@ -794,7 +795,7 @@ func TestTerragruntHelp(t *testing.T) {

output := &bytes.Buffer{}
opts := options.NewTerragruntOptionsWithWriters(output, os.Stderr)
app := cli.NewApp(logger.CreateLogger(), opts)
app := cli.NewApp(logger.CreateLogger(), opts, venv.OSVenv())
err := app.Run(tc.args)
require.NoError(t, err, tc)

Expand Down Expand Up @@ -822,7 +823,7 @@ func TestTerraformHelp(t *testing.T) {
for _, tc := range testCases {
output := &bytes.Buffer{}
opts := options.NewTerragruntOptionsWithWriters(output, os.Stderr)
app := cli.NewApp(logger.CreateLogger(), opts)
app := cli.NewApp(logger.CreateLogger(), opts, venv.OSVenv())
err := app.Run(tc.args)
require.NoError(t, err)

Expand All @@ -836,7 +837,7 @@ func TestTerraformHelp_wrongHelpFlag(t *testing.T) {
output := &bytes.Buffer{}

opts := options.NewTerragruntOptionsWithWriters(output, os.Stderr)
app := cli.NewApp(logger.CreateLogger(), opts)
app := cli.NewApp(logger.CreateLogger(), opts, venv.OSVenv())

err := app.Run([]string{"terragrunt", "plan", "help"})
require.Error(t, err)
Expand All @@ -852,15 +853,15 @@ func setCommandAction(action clihelper.ActionFunc, cmds ...*clihelper.Command) {
func runAppTest(l log.Logger, args []string, opts *options.TerragruntOptions) (*options.TerragruntOptions, error) {
emptyAction := func(ctx context.Context, cliCtx *clihelper.Context) error { return nil }

terragruntCommands := commands.New(l, opts)
terragruntCommands := commands.New(l, opts, venv.OSVenv())
setCommandAction(emptyAction, terragruntCommands...)

app := clihelper.NewApp()
app.Writer = &bytes.Buffer{}
app.ErrWriter = &bytes.Buffer{}

app.Flags = append(global.NewFlags(l, opts, nil), run.NewFlags(l, opts, nil)...)
app.Commands = terragruntCommands.WrapAction(commands.WrapWithTelemetry(l, opts))
app.Commands = terragruntCommands.WrapAction(commands.WrapWithTelemetry(l, opts, venv.OSVenv()))
app.OsExiter = cli.OSExiter
app.Action = func(ctx context.Context, cliCtx *clihelper.Context) error {
for _, arg := range cliCtx.Args() {
Expand Down Expand Up @@ -921,7 +922,7 @@ func TestAutocomplete(t *testing.T) { //nolint:paralleltest

output := &bytes.Buffer{}
opts := options.NewTerragruntOptionsWithWriters(output, os.Stderr)
app := cli.NewApp(logger.CreateLogger(), opts)
app := cli.NewApp(logger.CreateLogger(), opts, venv.OSVenv())

app.Commands = app.Commands.FilterByNames([]string{"hcl", "render", "run"})

Expand Down
23 changes: 12 additions & 11 deletions internal/cli/commands/aws-provider-patch/aws-provider-patch.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/gruntwork-io/terragrunt/internal/discovery"
"github.com/gruntwork-io/terragrunt/internal/prepare"
"github.com/gruntwork-io/terragrunt/internal/report"
"github.com/gruntwork-io/terragrunt/internal/runner/run"
"github.com/gruntwork-io/terragrunt/internal/util"
"github.com/gruntwork-io/terragrunt/pkg/config"
"github.com/gruntwork-io/terragrunt/pkg/log"
Expand All @@ -26,42 +27,42 @@ import (

const defaultKeyParts = 2

func Run(ctx context.Context, l log.Logger, opts *options.TerragruntOptions) error {
func Run(ctx context.Context, l log.Logger, v run.Venv, opts *options.TerragruntOptions) error {
if opts.RunAll {
return runAll(ctx, l, opts)
return runAll(ctx, l, v, opts)
}

return runSingle(ctx, l, opts)
return runSingle(ctx, l, v, opts)
}

func runSingle(ctx context.Context, l log.Logger, opts *options.TerragruntOptions) error {
prepared, err := prepare.PrepareConfig(ctx, l, opts)
func runSingle(ctx context.Context, l log.Logger, v run.Venv, opts *options.TerragruntOptions) error {
prepared, err := prepare.PrepareConfig(ctx, l, v, opts)
if err != nil {
return err
}

r := report.NewReport()

updatedOpts, err := prepare.PrepareSource(ctx, l, prepared.Opts, prepared.Cfg, r)
updatedOpts, err := prepare.PrepareSource(ctx, l, v, prepared.Opts, prepared.Cfg, r)
if err != nil {
return err
}

runCfg := prepared.Cfg.ToRunConfig(l)

if err := prepare.PrepareGenerate(l, updatedOpts, runCfg); err != nil {
if err := prepare.PrepareGenerate(l, v, updatedOpts, runCfg); err != nil {
return err
}

if err := prepare.PrepareInit(ctx, l, opts, updatedOpts, runCfg, r); err != nil {
if err := prepare.PrepareInit(ctx, l, v, opts, updatedOpts, runCfg, r); err != nil {
return err
}

return runAwsProviderPatch(l, updatedOpts)
}

func runAll(ctx context.Context, l log.Logger, opts *options.TerragruntOptions) error {
d := discovery.NewDiscovery(opts.WorkingDir)
func runAll(ctx context.Context, l log.Logger, v run.Venv, opts *options.TerragruntOptions) error {
d := discovery.NewDiscovery(opts.WorkingDir).WithExec(v.Exec)

components, err := d.Discover(ctx, l, opts)
if err != nil {
Expand All @@ -83,7 +84,7 @@ func runAll(ctx context.Context, l log.Logger, opts *options.TerragruntOptions)

unitOpts.TerragruntConfigPath = filepath.Join(unit.Path(), configFilename)

if err := runSingle(ctx, l, unitOpts); err != nil {
if err := runSingle(ctx, l, v, unitOpts); err != nil {
if opts.FailFast {
return err
}
Expand Down
6 changes: 4 additions & 2 deletions internal/cli/commands/aws-provider-patch/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ import (
"github.com/gruntwork-io/terragrunt/internal/cli/flags"
"github.com/gruntwork-io/terragrunt/internal/cli/flags/shared"
"github.com/gruntwork-io/terragrunt/internal/clihelper"
"github.com/gruntwork-io/terragrunt/internal/runner/run"
"github.com/gruntwork-io/terragrunt/internal/strict/controls"
"github.com/gruntwork-io/terragrunt/internal/venv"
"github.com/gruntwork-io/terragrunt/pkg/log"
"github.com/gruntwork-io/terragrunt/pkg/options"
)
Expand All @@ -62,7 +64,7 @@ func NewFlags(l log.Logger, opts *options.TerragruntOptions, prefix flags.Prefix
}
}

func NewCommand(l log.Logger, opts *options.TerragruntOptions) *clihelper.Command {
func NewCommand(l log.Logger, opts *options.TerragruntOptions, v venv.Venv) *clihelper.Command {
control := controls.NewDeprecatedCommand(CommandName)
opts.StrictControls.FilterByNames(controls.DeprecatedCommands, controls.CLIRedesign, CommandName).AddSubcontrols(control)

Expand All @@ -82,7 +84,7 @@ func NewCommand(l log.Logger, opts *options.TerragruntOptions) *clihelper.Comman
return nil
},
Action: func(ctx context.Context, _ *clihelper.Context) error {
return Run(ctx, l, opts.OptionsFromContext(ctx))
return Run(ctx, l, run.FromRoot(v), opts.OptionsFromContext(ctx))
},
DisabledErrorOnUndefinedFlag: true,
}
Expand Down
5 changes: 3 additions & 2 deletions internal/cli/commands/backend/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,21 @@ import (
"github.com/gruntwork-io/terragrunt/internal/cli/commands/backend/delete"
"github.com/gruntwork-io/terragrunt/internal/cli/commands/backend/migrate"
"github.com/gruntwork-io/terragrunt/internal/clihelper"
"github.com/gruntwork-io/terragrunt/internal/venv"
"github.com/gruntwork-io/terragrunt/pkg/log"
"github.com/gruntwork-io/terragrunt/pkg/options"
)

const CommandName = "backend"

func NewCommand(l log.Logger, opts *options.TerragruntOptions) *clihelper.Command {
func NewCommand(l log.Logger, opts *options.TerragruntOptions, v venv.Venv) *clihelper.Command {
return &clihelper.Command{
Name: CommandName,
Usage: "Interact with OpenTofu/Terraform backend infrastructure.",
Subcommands: clihelper.Commands{
bootstrap.NewCommand(l, opts),
delete.NewCommand(l, opts),
migrate.NewCommand(l, opts),
migrate.NewCommand(l, opts, v),
},
Action: clihelper.ShowCommandHelp,
}
Expand Down
5 changes: 3 additions & 2 deletions internal/cli/commands/backend/migrate/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/gruntwork-io/terragrunt/internal/cli/flags"
"github.com/gruntwork-io/terragrunt/internal/cli/flags/shared"
"github.com/gruntwork-io/terragrunt/internal/clihelper"
"github.com/gruntwork-io/terragrunt/internal/venv"
"github.com/gruntwork-io/terragrunt/pkg/log"
"github.com/gruntwork-io/terragrunt/pkg/options"
)
Expand Down Expand Up @@ -40,7 +41,7 @@ func NewFlags(l log.Logger, opts *options.TerragruntOptions, prefix flags.Prefix
)
}

func NewCommand(l log.Logger, opts *options.TerragruntOptions) *clihelper.Command {
func NewCommand(l log.Logger, opts *options.TerragruntOptions, v venv.Venv) *clihelper.Command {
cmd := &clihelper.Command{
Name: CommandName,
Usage: "Migrate OpenTofu/Terraform state from one location to another.",
Expand All @@ -57,7 +58,7 @@ func NewCommand(l log.Logger, opts *options.TerragruntOptions) *clihelper.Comman
return errors.New(usageText)
}

return Run(ctx, l, srcPath, dstPath, opts.OptionsFromContext(ctx))
return Run(ctx, l, v, srcPath, dstPath, opts.OptionsFromContext(ctx))
},
}

Expand Down
18 changes: 16 additions & 2 deletions internal/cli/commands/backend/migrate/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

"github.com/gruntwork-io/terragrunt/internal/configbridge"
"github.com/gruntwork-io/terragrunt/internal/runner"
"github.com/gruntwork-io/terragrunt/internal/runner/run"
"github.com/gruntwork-io/terragrunt/internal/venv"

"errors"

Expand All @@ -17,7 +19,16 @@
"github.com/gruntwork-io/terragrunt/pkg/options"
)

func Run(ctx context.Context, l log.Logger, srcPath, dstPath string, opts *options.TerragruntOptions) error {
// Run migrates Terraform/OpenTofu state from srcPath to dstPath. v is the
// virtualized environment used for the underlying stack runner build and
// the state pull/push terraform invocations.
func Run(

Check failure on line 25 in internal/cli/commands/backend/migrate/migrate.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 17 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=gruntwork-io_terragrunt&issues=AZ4hcuhKLKV5nElQS5JT&open=AZ4hcuhKLKV5nElQS5JT&pullRequest=6089
ctx context.Context,
l log.Logger,
v venv.Venv,
srcPath, dstPath string,
opts *options.TerragruntOptions,
) error {
var err error

srcPath, err = util.CanonicalPath(srcPath, opts.WorkingDir)
Expand All @@ -34,7 +45,7 @@

l.Debugf("Destination unit path %s", dstPath)

rnr, err := runner.NewStackRunner(ctx, l, opts)
rnr, err := runner.NewStackRunner(ctx, l, run.FromRoot(v), opts)
if err != nil {
return err
}
Expand All @@ -60,6 +71,7 @@
}

_, srcPctx := configbridge.NewParsingContext(ctx, l, srcOpts)
srcPctx.Venv = v

srcRemoteState, err := config.ParseRemoteState(ctx, l, srcPctx)
if err != nil {
Expand All @@ -76,6 +88,7 @@
srcOpts.WorkingDir = srcPctx.WorkingDir

_, dstPctx := configbridge.NewParsingContext(ctx, l, dstOpts)
dstPctx.Venv = v

dstRemoteState, err := config.ParseRemoteState(ctx, l, dstPctx)
if err != nil {
Expand Down Expand Up @@ -105,6 +118,7 @@

return srcRemoteState.Migrate(
ctx, l,
v.Exec,
configbridge.RemoteStateOptsFromOpts(srcOpts),
configbridge.RemoteStateOptsFromOpts(dstOpts),
dstRemoteState,
Expand Down
5 changes: 4 additions & 1 deletion internal/cli/commands/catalog/tui/redesign/scaffold.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"io"

"github.com/gruntwork-io/terragrunt/internal/cli/commands/scaffold"
"github.com/gruntwork-io/terragrunt/internal/venv"
"github.com/gruntwork-io/terragrunt/pkg/log"
"github.com/gruntwork-io/terragrunt/pkg/options"
)
Expand Down Expand Up @@ -48,7 +49,9 @@

c.logger.Debugf("Scaffolding component: %q", c.component.TerraformSourcePath())

return scaffold.Run(context.Background(), c.logger, c.opts, c.component.TerraformSourcePath(), "")
// TODO: thread venv from the CLI entrypoint through the catalog TUI

Check warning on line 52 in internal/cli/commands/catalog/tui/redesign/scaffold.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Complete the task associated to this TODO comment.

See more on https://sonarcloud.io/project/issues?id=gruntwork-io_terragrunt&issues=AZ4hcumwLKV5nElQS5JU&open=AZ4hcumwLKV5nElQS5JU&pullRequest=6089
// so this leaf participates in the root virtualized environment.
return scaffold.Run(context.Background(), c.logger, venv.OSVenv(), c.opts, c.component.TerraformSourcePath(), "")
Comment on lines +52 to +54

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Line 54 still bypasses the caller's virtualized environment.

Using venv.OSVenv() here means the non-interactive catalog scaffold path still executes against the real OS-backed FS/exec instead of the root venv this PR is threading through the CLI. That leaves this leaf outside the new side-effect boundary.

Suggested direction
 type scaffoldCmd struct {
 	component *Component
 	opts      *options.TerragruntOptions
 	logger    log.Logger
+	v         venv.Venv
 	plan      *scaffold.Plan
 	values    map[string]string
 }

-func newScaffoldCmd(l log.Logger, opts *options.TerragruntOptions, c *Component) *scaffoldCmd {
-	return &scaffoldCmd{component: c, opts: opts, logger: l}
+func newScaffoldCmd(l log.Logger, v venv.Venv, opts *options.TerragruntOptions, c *Component) *scaffoldCmd {
+	return &scaffoldCmd{component: c, opts: opts, logger: l, v: v}
 }
@@
-	return scaffold.Run(context.Background(), c.logger, venv.OSVenv(), c.opts, c.component.TerraformSourcePath(), "")
+	return scaffold.Run(context.Background(), c.logger, c.v, c.opts, c.component.TerraformSourcePath(), "")
🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis

[warning] 52-52: Complete the task associated to this TODO comment.

See more on https://sonarcloud.io/project/issues?id=gruntwork-io_terragrunt&issues=AZ4hcumwLKV5nElQS5JU&open=AZ4hcumwLKV5nElQS5JU&pullRequest=6089

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/commands/catalog/tui/redesign/scaffold.go` around lines 52 - 54,
The call to scaffold.Run uses venv.OSVenv(), which bypasses the caller's
virtualized environment; replace that argument with the propagated/owned venv
instance instead (e.g., use the CLI-threaded venv field or parameter rather than
venv.OSVenv()). Locate the scaffold.Run invocation and change the third
parameter from venv.OSVenv() to the root/provided venv object (for example
c.venv or the venv param you thread from the CLI entrypoint), or add a venv
parameter to the enclosing function and pass it through so this leaf executes
inside the same virtualized environment.

}

func (c *scaffoldCmd) SetStdin(io.Reader) {}
Expand Down
3 changes: 2 additions & 1 deletion internal/cli/commands/catalog/tui/redesign/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (

"github.com/gruntwork-io/terragrunt/internal/cli/commands/catalog/tui/components/buttonbar"
"github.com/gruntwork-io/terragrunt/internal/cli/commands/scaffold"
"github.com/gruntwork-io/terragrunt/internal/venv"
"github.com/gruntwork-io/terragrunt/internal/vfs"
"github.com/gruntwork-io/terragrunt/pkg/config"
"github.com/gruntwork-io/terragrunt/pkg/log"
Expand Down Expand Up @@ -611,7 +612,7 @@ func discoverFormCmd(ctx context.Context, l log.Logger, opts *options.Terragrunt
func discoverModuleFields(ctx context.Context, l log.Logger, opts *options.TerragruntOptions, c *Component) tea.Msg {
quiet := l.WithOptions(log.WithOutput(io.Discard))

plan, err := scaffold.Prepare(ctx, quiet, opts, c.TerraformSourcePath(), "")
plan, err := scaffold.Prepare(ctx, quiet, venv.OSVenv(), opts, c.TerraformSourcePath(), "")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Line 615 still prepares modules in the real OS environment.

scaffold.Prepare performs download/parse work, so hard-coding venv.OSVenv() here means the interactive catalog flow still ignores the caller's virtualized FS/exec even though the rest of this PR is threading one through CLI side effects.

Suggested direction
-func discoverFormCmd(ctx context.Context, l log.Logger, opts *options.TerragruntOptions, c *Component) tea.Cmd {
+func discoverFormCmd(ctx context.Context, l log.Logger, v venv.Venv, opts *options.TerragruntOptions, c *Component) tea.Cmd {
 	return func() tea.Msg {
 		if c.Kind.IsCopyable() {
 			return discoverValuesFields(c)
 		}

-		return discoverModuleFields(ctx, l, opts, c)
+		return discoverModuleFields(ctx, l, v, opts, c)
 	}
 }
@@
-func discoverModuleFields(ctx context.Context, l log.Logger, opts *options.TerragruntOptions, c *Component) tea.Msg {
+func discoverModuleFields(ctx context.Context, l log.Logger, v venv.Venv, opts *options.TerragruntOptions, c *Component) tea.Msg {
 	quiet := l.WithOptions(log.WithOutput(io.Discard))

-	plan, err := scaffold.Prepare(ctx, quiet, venv.OSVenv(), opts, c.TerraformSourcePath(), "")
+	plan, err := scaffold.Prepare(ctx, quiet, v, opts, c.TerraformSourcePath(), "")

Then pass the same handle from the TUI model/entrypoint into discoverFormCmd.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/commands/catalog/tui/redesign/update.go` at line 615,
scaffold.Prepare is being called with the real OS environment via venv.OSVenv(),
so the interactive TUI still runs downloads/parsing in the real filesystem/exec;
change the call site in update.go to accept and forward the virtualized
environment handle that the TUI model/entrypoint provides instead of
venv.OSVenv(), and ensure that same handle is threaded into discoverFormCmd so
all scaffold.Prepare invocations use the injected virtual FS/exec rather than
the hard-coded OS environment.

if err != nil {
return formDiscoveryErrMsg{err: err}
}
Expand Down
Loading
Loading