Skip to content

chore: iac cli package#5462

Merged
denis256 merged 30 commits intomainfrom
iac-package
Jan 30, 2026
Merged

chore: iac cli package#5462
denis256 merged 30 commits intomainfrom
iac-package

Conversation

@denis256
Copy link
Copy Markdown
Member

@denis256 denis256 commented Jan 29, 2026

Description

  • extracted dedicated internal package for handling iac cli package
  • reduced bloated clihelper package

TODOs

Read the Gruntwork contribution guidelines.

  • I authored this code entirely myself
  • I am submitting code based on open source software (e.g. MIT, MPL-2.0, Apache)]
  • I am adding or upgrading a dependency or adapted code and confirm it has a compatible open source license
  • Update the docs.
  • Run the relevant tests successfully, including pre-commit checks.
  • Include release notes. If this PR is backward incompatible, include a migration guide.

Release Notes (draft)

Added / Removed / Updated [X].

Migration Guide

Summary by CodeRabbit

Release Notes

  • Refactor

    • Restructured internal architecture for managing Terraform CLI arguments with improved organization and maintainability.
  • Tests

    • Updated and expanded test coverage to validate argument handling and configuration across the refactored codebase.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link
Copy Markdown

vercel bot commented Jan 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
terragrunt-docs Ready Ready Preview, Comment Jan 30, 2026 1:48pm

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jan 29, 2026

📝 Walkthrough

Walkthrough

This PR relocates the IacArgs type and its associated utilities from internal/clihelper to a new internal/iacargs package. All call sites across the codebase are updated to use iacargs.New() instead of clihelper.NewIacArgs(), maintaining functional equivalence while reorganizing the code structure.

Changes

Cohort / File(s) Summary
IacArgs removal from clihelper
internal/clihelper/args.go, internal/clihelper/args_test.go
Deleted entire IacArgs type, constructor NewIacArgs, all associated methods (SetCommand, AppendFlag, HasFlag, RemoveFlag, etc.), helper functions (normalizeFlag, isFlag, extractFlagName), and related constants (minFlagLen, valueTakingFlags, knownSubCommands). Removed corresponding test suite (~532 lines of tests).
IacArgs creation in iacargs package
internal/iacargs/iacargs.go, internal/iacargs/iacargs_test.go, internal/iacargs/boolean_args_test.go
New package with complete IacArgs implementation including constructor New(), fluent builder methods, flag/argument parsing logic, plan file detection, normalization, and destroy command identification. Comprehensive test coverage added; existing test file updated to reference iacargs instead of clihelper.
CLI and command initialization
internal/cli/app_test.go, internal/cli/commands/commands.go, internal/shell/run_cmd_test.go, internal/providercache/provider_cache.go
Updated TerraformCliArgs initialization from clihelper.NewIacArgs(...) to iacargs.New(...) with corresponding import changes.
Runner implementations
internal/runner/common/unit_runner.go, internal/runner/run/run.go, internal/runner/run/run_test.go, internal/runner/runnerpool/runner.go, internal/runner/runall/runall.go
Replaced clihelper.NewIacArgs(...) with iacargs.New(...); refactored destroy command detection to use TerraformCliArgs.IsDestroyCommand() method instead of standalone helper; updated flag constant references to iacargs.SingleDashFlag; removed isDestroyCommand helper function from runner.go.
Configuration and options
pkg/config/config_helpers_test.go, pkg/config/dependency.go, pkg/options/options.go, pkg/options/options_test.go
Updated constructor calls from clihelper.NewIacArgs(...) to iacargs.New(...); changed TerraformCliArgs field type in TerragruntOptions from *clihelper.IacArgs to *iacargs.IacArgs; updated ShouldCopyLockFile signature in run.go to accept *iacargs.IacArgs; updated mergeCommandAndSubCommand helper signature in options.go; replaced clihelper.IsKnownSubCommand with iacargs.IsKnownSubCommand.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • ThisGuyCodes
🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.95% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'chore: iac cli package' is vague and generic, using non-descriptive terms that don't clearly convey the specific changes made. Use a more descriptive title that specifically mentions the extraction of the iacargs package and refactoring of clihelper, such as 'chore: extract iacargs package and simplify clihelper'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The description covers the main objectives (extracted dedicated iacargs package, reduced clihelper) and includes most required template sections, though release notes and migration guide are not completed.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch iac-package

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@denis256 denis256 changed the title chore: Iac package chore: iac cli package Jan 29, 2026
@denis256 denis256 merged commit b2e557a into main Jan 30, 2026
49 of 52 checks passed
@denis256 denis256 deleted the iac-package branch January 30, 2026 15:11
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@internal/iacargs/iacargs.go`:
- Around line 362-366: IsDestroyCommand currently checks
a.Contains("-"+CommandNameDestroy), which fails for forms like -destroy=true;
update IsDestroyCommand to use the flag-normalizing helper HasFlag with the flag
name (e.g., HasFlag(CommandNameDestroy) or HasFlag("destroy")) instead of
Contains so that variants like -destroy=true are recognized; reference
IsDestroyCommand, Contains, HasFlag, and CommandNameDestroy when making this
change.

In `@internal/runner/runnerpool/runner.go`:
- Around line 334-336: The call to
terragruntOptions.TerraformCliArgs.IsDestroyCommand can panic if
TerraformCliArgs is nil; update the check around IsDestroyCommand in the code
that currently calls applyPreventDestroyExclusions(l, units) so it first
verifies terragruntOptions != nil && terragruntOptions.TerraformCliArgs != nil
before calling IsDestroyCommand. Locate the use of terragruntOptions and the
IsDestroyCommand invocation and short-circuit to skip
applyPreventDestroyExclusions when TerraformCliArgs is nil to match existing
defensive checks in InsertTerraformCliArgs/AppendTerraformCliArgs.

Comment on lines +362 to +366
// IsDestroyCommand returns true if this represents a destroy operation.
// It checks both the command name and the -destroy flag.
func (a *IacArgs) IsDestroyCommand(cmd string) bool {
return cmd == CommandNameDestroy || a.Contains("-"+CommandNameDestroy)
}
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

🧩 Analysis chain

🏁 Script executed:

# Find and examine the IacArgs implementation
cat -n internal/iacargs/iacargs.go | head -100

Repository: gruntwork-io/terragrunt

Length of output: 3391


🏁 Script executed:

# Search for HasFlag and Contains implementations
rg -A 10 "func.*HasFlag" internal/iacargs/

Repository: gruntwork-io/terragrunt

Length of output: 2503


🏁 Script executed:

# Check if there are other destroy-related methods
rg -i "destroy" internal/iacargs/iacargs.go

Repository: gruntwork-io/terragrunt

Length of output: 562


🌐 Web query:

Terraform CLI destroy command flag formats accepted (-destroy, -destroy=true, --destroy)

💡 Result:

  • Supported (Terraform CLI “destroy mode” flag):

    • terraform apply -destroy / terraform plan -destroy (enables destroy mode). The terraform destroy command is effectively terraform apply -destroy. [1][2]
    • terraform apply -destroy=true / terraform plan -destroy=true (explicit boolean form). Terraform’s CLI supports boolean flags with an explicit =true|false form (e.g., -refresh=false), and -destroy is a boolean planning-mode switch. [2][3]
  • Not supported / not documented:

    • --destroy (Terraform’s official docs consistently show single-dash long options like -destroy, not GNU-style --destroy). [1][2]

Sources:
[1] Terraform destroy command docs (alias for terraform apply -destroy) — HashiCorp Developer docs.
[2] Terraform plan command docs (activate destroy mode using -destroy) — HashiCorp Developer docs.
[3] Terraform plan command docs (example boolean option format -refresh=false) — HashiCorp Developer docs.


🏁 Script executed:

rg -A 10 "func.*Contains" internal/iacargs/iacargs.go

Repository: gruntwork-io/terragrunt

Length of output: 467


🏁 Script executed:

# Also check the test file for Contains to understand its behavior
rg -B 3 -A 10 "Contains" internal/iacargs/iacargs_test.go

Repository: gruntwork-io/terragrunt

Length of output: 993


Destroy detection misses -destroy=true variant

IsDestroyCommand uses Contains which performs exact matching on the Flags list. When -destroy=true is passed, the exact token -destroy is not present, so the check fails and bypasses destroy protections. Use HasFlag("destroy") which normalizes and extracts flag names, properly handling -destroy=true format.

🛠️ Suggested fix
 func (a *IacArgs) IsDestroyCommand(cmd string) bool {
-	return cmd == CommandNameDestroy || a.Contains("-"+CommandNameDestroy)
+	if cmd == "" {
+		cmd = a.Command
+	}
+	return cmd == CommandNameDestroy || a.HasFlag(CommandNameDestroy)
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// IsDestroyCommand returns true if this represents a destroy operation.
// It checks both the command name and the -destroy flag.
func (a *IacArgs) IsDestroyCommand(cmd string) bool {
return cmd == CommandNameDestroy || a.Contains("-"+CommandNameDestroy)
}
// IsDestroyCommand returns true if this represents a destroy operation.
// It checks both the command name and the -destroy flag.
func (a *IacArgs) IsDestroyCommand(cmd string) bool {
if cmd == "" {
cmd = a.Command
}
return cmd == CommandNameDestroy || a.HasFlag(CommandNameDestroy)
}
🤖 Prompt for AI Agents
In `@internal/iacargs/iacargs.go` around lines 362 - 366, IsDestroyCommand
currently checks a.Contains("-"+CommandNameDestroy), which fails for forms like
-destroy=true; update IsDestroyCommand to use the flag-normalizing helper
HasFlag with the flag name (e.g., HasFlag(CommandNameDestroy) or
HasFlag("destroy")) instead of Contains so that variants like -destroy=true are
recognized; reference IsDestroyCommand, Contains, HasFlag, and
CommandNameDestroy when making this change.

Comment on lines +334 to 336
if terragruntOptions.TerraformCliArgs.IsDestroyCommand(terragruntOptions.TerraformCommand) {
applyPreventDestroyExclusions(l, units)
}
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the struct definition and initialization of TerraformCliArgs
rg -n --type=go -A 5 'type.*Options struct' | head -100

Repository: gruntwork-io/terragrunt

Length of output: 5228


🏁 Script executed:

#!/bin/bash
# Look for TerraformCliArgs initialization patterns
rg -n --type=go 'TerraformCliArgs.*=' | head -40

Repository: gruntwork-io/terragrunt

Length of output: 2525


🏁 Script executed:

#!/bin/bash
# Check the specific file and surrounding context
cat -n internal/runner/runnerpool/runner.go | sed -n '320,350p'

Repository: gruntwork-io/terragrunt

Length of output: 1484


🏁 Script executed:

#!/bin/bash
# Search for nil checks on TerraformCliArgs
rg -n --type=go 'TerraformCliArgs.*!=' | head -20

Repository: gruntwork-io/terragrunt

Length of output: 173


🏁 Script executed:

#!/bin/bash
# Get the function signature and more context around lines 334-336
sed -n '300,340p' internal/runner/runnerpool/runner.go | cat -n

Repository: gruntwork-io/terragrunt

Length of output: 1732


🏁 Script executed:

#!/bin/bash
# Find the function definition that contains line 334
sed -n '1,334p' internal/runner/runnerpool/runner.go | tac | head -100 | tac | grep -n "^func"

Repository: gruntwork-io/terragrunt

Length of output: 170


🏁 Script executed:

#!/bin/bash
# Get more complete function context
sed -n '280,365p' internal/runner/runnerpool/runner.go | cat -n

Repository: gruntwork-io/terragrunt

Length of output: 3228


🏁 Script executed:

#!/bin/bash
# Get the full function signature of NewRunnerPoolStack
sed -n '237,280p' internal/runner/runnerpool/runner.go | cat -n

Repository: gruntwork-io/terragrunt

Length of output: 1834


🏁 Script executed:

#!/bin/bash
# Look for where NewRunnerPoolStack is called
rg -n 'NewRunnerPoolStack' --type=go -B 2 -A 2

Repository: gruntwork-io/terragrunt

Length of output: 1175


🏁 Script executed:

#!/bin/bash
# Check TerraformCliArgs field definition in TerragruntOptions struct
sed -n '99,200p' pkg/options/options.go | grep -A 150 'type TerragruntOptions struct' | head -120

Repository: gruntwork-io/terragrunt

Length of output: 4569


🏁 Script executed:

#!/bin/bash
# Check the builder_helpers.go context where NewRunnerPoolStack is called
sed -n '195,210p' internal/runner/runnerpool/builder_helpers.go | cat -n

Repository: gruntwork-io/terragrunt

Length of output: 540


🏁 Script executed:

#!/bin/bash
# Find where tgOpts comes from in builder_helpers.go
sed -n '1,202p' internal/runner/runnerpool/builder_helpers.go | tail -60 | head -50 | cat -n

Repository: gruntwork-io/terragrunt

Length of output: 1708


🏁 Script executed:

#!/bin/bash
# Search for the full context of the call to NewRunnerPoolStack in builder_helpers
rg -n -B 10 'NewRunnerPoolStack' internal/runner/runnerpool/builder_helpers.go | head -40

Repository: gruntwork-io/terragrunt

Length of output: 481


🏁 Script executed:

#!/bin/bash
# Check if there are other places where TerraformCliArgs is dereferenced without nil check
rg -n 'TerraformCliArgs\.' --type=go | grep -v '!=' | grep -v '==' | head -30

Repository: gruntwork-io/terragrunt

Length of output: 2766


🏁 Script executed:

#!/bin/bash
# Look for the main entry point to understand how TerragruntOptions is initialized
rg -n 'func main' --type=go -A 30

Repository: gruntwork-io/terragrunt

Length of output: 4467


🏁 Script executed:

#!/bin/bash
# Check if there's initialization code in the CLI that creates/initializes options
rg -n 'TerragruntOptions\s*{' --type=go -A 5 | head -60

Repository: gruntwork-io/terragrunt

Length of output: 172


🏁 Script executed:

#!/bin/bash
# Search for where NewRunnerPoolStack is called from and trace back to see if options are initialized
rg -n 'createRunner\|NewRunnerPoolStack' --type=go -B 5 | head -80

Repository: gruntwork-io/terragrunt

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Find the NewTerragruntOptions function to see how TerraformCliArgs is initialized
rg -n 'func NewTerragruntOptions' --type=go -A 30

Repository: gruntwork-io/terragrunt

Length of output: 5901


🏁 Script executed:

#!/bin/bash
# Check pkg/options/options.go to see the initialization of TerraformCliArgs
sed -n '700,850p' pkg/options/options.go | cat -n

Repository: gruntwork-io/terragrunt

Length of output: 4768


🏁 Script executed:

#!/bin/bash
# Find NewTerragruntOptions initialization function
rg -n 'NewTerragruntOptions' --type=go | head -5

Repository: gruntwork-io/terragrunt

Length of output: 449


🏁 Script executed:

#!/bin/bash
# Check test files to see if TerragruntOptions is ever created without initialization
rg -n 'TerragruntOptions\{' --type=go -A 3 | head -40

Repository: gruntwork-io/terragrunt

Length of output: 2472


🏁 Script executed:

#!/bin/bash
# Look for any test files that might construct TerragruntOptions directly
rg -n '&options\.TerragruntOptions' --type=go -A 2 | head -40

Repository: gruntwork-io/terragrunt

Length of output: 2463


🏁 Script executed:

#!/bin/bash
# Check if IsDestroyCommand is ever called elsewhere with similar pattern
rg -n 'IsDestroyCommand' --type=go -B 2 -A 2

Repository: gruntwork-io/terragrunt

Length of output: 2321


🏁 Script executed:

#!/bin/bash
# Check if there are specific callers of NewRunnerPoolStack and trace back the source of terragruntOptions
rg -n 'createRunner' --type=go -B 20 | grep -A 20 'func '

Repository: gruntwork-io/terragrunt

Length of output: 1578


🏁 Script executed:

#!/bin/bash
# Find all callers of createRunner to understand the flow
rg -n 'createRunner\(' --type=go -B 5 -A 2 | head -60

Repository: gruntwork-io/terragrunt

Length of output: 1189


🏁 Script executed:

#!/bin/bash
# Check if there's a nil guard on line 665 where TerraformCliArgs is also dereferenced
sed -n '660,670p' internal/runner/runnerpool/runner.go | cat -n

Repository: gruntwork-io/terragrunt

Length of output: 647


🏁 Script executed:

#!/bin/bash
# Check what happens in the test that calls NewRunnerPoolStack
sed -n '30,50p' internal/runner/runnerpool/runner_test.go | cat -n

Repository: gruntwork-io/terragrunt

Length of output: 693


🏁 Script executed:

#!/bin/bash
# Check if there are any other paths where TerraformCliArgs might not be initialized
rg -n 'TerraformCliArgs.*nil' --type=go -B 2 -A 2

Repository: gruntwork-io/terragrunt

Length of output: 775


🏁 Script executed:

#!/bin/bash
# Verify Build function to ensure it always properly initializes terragruntOptions
sed -n '11,30p' internal/runner/runnerpool/builder.go | cat -n

Repository: gruntwork-io/terragrunt

Length of output: 768


Add nil guard for TerraformCliArgs to prevent potential panic.

While TerraformCliArgs is initialized by the standard constructors in production code paths, defensive nil checks already exist elsewhere in the codebase (InsertTerraformCliArgs, AppendTerraformCliArgs). This is good practice given the possibility of direct struct construction without proper initialization.

🔧 Suggested guard
-	if terragruntOptions.TerraformCliArgs.IsDestroyCommand(terragruntOptions.TerraformCommand) {
+	isDestroy := terragruntOptions.TerraformCommand == tf.CommandNameDestroy
+	if terragruntOptions.TerraformCliArgs != nil {
+		isDestroy = terragruntOptions.TerraformCliArgs.IsDestroyCommand(terragruntOptions.TerraformCommand)
+	}
+	if isDestroy {
 		applyPreventDestroyExclusions(l, units)
 	}
🤖 Prompt for AI Agents
In `@internal/runner/runnerpool/runner.go` around lines 334 - 336, The call to
terragruntOptions.TerraformCliArgs.IsDestroyCommand can panic if
TerraformCliArgs is nil; update the check around IsDestroyCommand in the code
that currently calls applyPreventDestroyExclusions(l, units) so it first
verifies terragruntOptions != nil && terragruntOptions.TerraformCliArgs != nil
before calling IsDestroyCommand. Locate the use of terragruntOptions and the
IsDestroyCommand invocation and short-circuit to skip
applyPreventDestroyExclusions when TerraformCliArgs is nil to match existing
defensive checks in InsertTerraformCliArgs/AppendTerraformCliArgs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants