Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
3befae0
add parallel unit resolver execution
denis256 Oct 30, 2025
a0cb6c3
Parallel auth provider execution
denis256 Oct 30, 2025
3a40eb7
chore: pr comments
denis256 Oct 30, 2025
1569858
chore: tests cleanup
denis256 Oct 30, 2025
9d69289
chore: added configuration of parallelism
denis256 Oct 30, 2025
748b419
chore: lint fixes
denis256 Oct 30, 2025
9f5aed5
unit resolver simplifications
denis256 Oct 30, 2025
e8f2d7a
unit resolver simplifications
denis256 Oct 30, 2025
58202ef
simplified unit resolver
denis256 Oct 30, 2025
9b83f96
Unit resolver separation
denis256 Oct 31, 2025
a53c896
chore: failint tests fixes
denis256 Oct 31, 2025
8d6726b
chore: tests fixes
denis256 Oct 31, 2025
0051838
Failing tests fixes
denis256 Oct 31, 2025
68397a5
fix: run all tests fixes
denis256 Oct 31, 2025
ec3e9ca
Glob file format
denis256 Oct 31, 2025
58477de
added debug messages
denis256 Oct 31, 2025
13a08c9
Debug code cleanup
denis256 Oct 31, 2025
3075c63
Filtering update
denis256 Oct 31, 2025
7bab731
chore: tests cleanup
denis256 Oct 31, 2025
5e72e5f
tests fixes
denis256 Nov 1, 2025
8ae7b13
tests fixes
denis256 Nov 1, 2025
daf27e2
Merge remote-tracking branch 'origin/main' into auth-cmd-parallel
denis256 Nov 1, 2025
0d6a127
Report test fixes
denis256 Nov 1, 2025
05bc73d
chore: runner controller error checks
denis256 Nov 1, 2025
92cfdf9
controller cleanup
denis256 Nov 1, 2025
1e01297
Updated runner pool error handling
denis256 Nov 1, 2025
b54a67d
Logs cleanup
denis256 Nov 3, 2025
8c76d01
chore: unit resolver simplification
denis256 Nov 3, 2025
424cdb6
chore: resolver simplification
denis256 Nov 3, 2025
3876117
chore: unit resolver error handling
denis256 Nov 3, 2025
a6e2c0f
lint fixes
denis256 Nov 3, 2025
2ce742b
chore: simplified errors handling
denis256 Nov 3, 2025
12c1f20
chore: reduced controller complexity
denis256 Nov 3, 2025
3fa000e
chore: usage of internal error package to make easy error handling
denis256 Nov 3, 2025
1ac377b
Merge remote-tracking branch 'origin/main' into auth-cmd-parallel
denis256 Nov 3, 2025
23d22f9
chore: pr comments
denis256 Nov 3, 2025
7ecf501
Merge branch 'main' into auth-cmd-parallel
denis256 Nov 3, 2025
2154e5c
chore: PR comments
denis256 Nov 3, 2025
677afe3
Merge remote-tracking branch 'origin/auth-cmd-parallel' into auth-cmd…
denis256 Nov 3, 2025
51235fc
Unit resolver cleanup
denis256 Nov 4, 2025
b69d977
chore: pr comments
denis256 Nov 4, 2025
599b61a
Revert "chore: pr comments"
denis256 Nov 4, 2025
da64c33
chore: unit resolver simplifications
denis256 Nov 4, 2025
fda5658
chore: Unit path fix
denis256 Nov 4, 2025
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
16 changes: 8 additions & 8 deletions cli/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ func setupAutoProviderCacheDir(ctx context.Context, l log.Logger, opts *options.

// Check if OpenTofu is being used
if tfImplementation != options.OpenTofuImpl {
return fmt.Errorf("auto provider cache dir requires OpenTofu, but detected %s", tfImplementation)
return errors.Errorf("auto provider cache dir requires OpenTofu, but detected %s", tfImplementation)
}

// Check OpenTofu version > 1.10
Expand All @@ -222,19 +222,19 @@ func setupAutoProviderCacheDir(ctx context.Context, l log.Logger, opts *options.

requiredVersion, err := version.NewVersion(minTofuVersionForAutoProviderCacheDir)
if err != nil {
return fmt.Errorf("failed to parse required version: %w", err)
return errors.Errorf("failed to parse required version: %w", err)
}

if terraformVersion.LessThan(requiredVersion) {
return fmt.Errorf("auto provider cache dir requires OpenTofu version >= 1.10, but found %s", terraformVersion)
return errors.Errorf("auto provider cache dir requires OpenTofu version >= 1.10, but found %s", terraformVersion)
}

// Set up the provider cache directory
providerCacheDir := opts.ProviderCacheDir
if providerCacheDir == "" {
cacheDir, err := util.GetCacheDir()
if err != nil {
return fmt.Errorf("failed to get cache directory: %w", err)
return errors.Errorf("failed to get cache directory: %w", err)
}

providerCacheDir = filepath.Join(cacheDir, "providers")
Expand All @@ -244,7 +244,7 @@ func setupAutoProviderCacheDir(ctx context.Context, l log.Logger, opts *options.
if !filepath.IsAbs(providerCacheDir) {
absPath, err := filepath.Abs(providerCacheDir)
if err != nil {
return fmt.Errorf("failed to get absolute path for provider cache directory: %w", err)
return errors.Errorf("failed to get absolute path for provider cache directory: %w", err)
}

providerCacheDir = absPath
Expand All @@ -254,7 +254,7 @@ func setupAutoProviderCacheDir(ctx context.Context, l log.Logger, opts *options.

// Create the cache directory if it doesn't exist
if err := os.MkdirAll(providerCacheDir, cacheDirMode); err != nil {
return fmt.Errorf("failed to create provider cache directory: %w", err)
return errors.Errorf("failed to create provider cache directory: %w", err)
}

// Initialize environment variables map if it's nil
Expand Down Expand Up @@ -389,7 +389,7 @@ func initialSetup(cliCtx *cli.Context, l log.Logger, opts *options.TerragruntOpt
if !doubleStarEnabled {
opts.IncludeDirs, err = util.GlobCanonicalPath(l, opts.WorkingDir, opts.IncludeDirs...)
if err != nil {
return fmt.Errorf("invalid include dirs: %w", err)
return errors.Errorf("invalid include dirs: %w", err)
}
}

Expand All @@ -406,7 +406,7 @@ func initialSetup(cliCtx *cli.Context, l log.Logger, opts *options.TerragruntOpt
if !doubleStarEnabled {
opts.ExcludeDirs, err = util.GlobCanonicalPath(l, opts.WorkingDir, opts.ExcludeDirs...)
if err != nil {
return fmt.Errorf("invalid exclude dirs: %w", err)
return errors.Errorf("invalid exclude dirs: %w", err)
}
}

Expand Down
15 changes: 14 additions & 1 deletion cli/commands/common/runall/runall.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,9 @@ func RunAllOnStack(ctx context.Context, l log.Logger, opts *options.TerragruntOp
}
}

return telemetry.TelemeterFromContext(ctx).Collect(ctx, "run_all_on_stack", map[string]any{
var runErr error

telemetryErr := telemetry.TelemeterFromContext(ctx).Collect(ctx, "run_all_on_stack", map[string]any{
"terraform_command": opts.TerraformCommand,
"working_dir": opts.WorkingDir,
}, func(ctx context.Context) error {
Expand All @@ -141,11 +143,22 @@ func RunAllOnStack(ctx context.Context, l log.Logger, opts *options.TerragruntOp

exitCode.Set(int(cli.ExitCodeGeneralError))

// Save error to potentially return after telemetry completes
runErr = err

// Return nil to allow telemetry and reporting to complete
return nil
}

return nil
})

// log telemetry error and continue execution
if telemetryErr != nil {
l.Warnf("Telemetry collection failed: %v", telemetryErr)
}

return runErr
}

// shouldSkipSummary determines if summary output should be skipped for programmatic interactions.
Expand Down
8 changes: 4 additions & 4 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -1991,19 +1991,19 @@ func (cfg *TerragruntConfig) ErrorsConfig() (*options.ErrorsConfig, error) {

// Validate retry settings
if retryBlock.MaxAttempts < 1 {
return nil, fmt.Errorf("cannot have less than 1 max retry in errors.retry %q, but you specified %d", retryBlock.Label, retryBlock.MaxAttempts)
return nil, errors.Errorf("cannot have less than 1 max retry in errors.retry %q, but you specified %d", retryBlock.Label, retryBlock.MaxAttempts)
}

if retryBlock.SleepIntervalSec < 0 {
return nil, fmt.Errorf("cannot sleep for less than 0 seconds in errors.retry %q, but you specified %d", retryBlock.Label, retryBlock.SleepIntervalSec)
return nil, errors.Errorf("cannot sleep for less than 0 seconds in errors.retry %q, but you specified %d", retryBlock.Label, retryBlock.SleepIntervalSec)
}

compiledPatterns := make([]*options.ErrorsPattern, 0, len(retryBlock.RetryableErrors))

for _, pattern := range retryBlock.RetryableErrors {
value, err := errorsPattern(pattern)
if err != nil {
return nil, fmt.Errorf("invalid retry pattern %q in block %q: %w",
return nil, errors.Errorf("invalid retry pattern %q in block %q: %w",
pattern, retryBlock.Label, err)
}

Expand Down Expand Up @@ -2042,7 +2042,7 @@ func (cfg *TerragruntConfig) ErrorsConfig() (*options.ErrorsConfig, error) {
for _, pattern := range ignoreBlock.IgnorableErrors {
value, err := errorsPattern(pattern)
if err != nil {
return nil, fmt.Errorf("invalid ignore pattern %q in block %q: %w",
return nil, errors.Errorf("invalid ignore pattern %q in block %q: %w",
pattern, ignoreBlock.Label, err)
}

Expand Down
49 changes: 33 additions & 16 deletions internal/discovery/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -429,34 +429,48 @@ func Parse(
parserOptions []hclparse.Option,
) error {
parseOpts := opts.Clone()
parseOpts.WorkingDir = c.Path()

// Determine working directory and config filename, supporting file paths and stack kind
componentPath := c.Path()

var workingDir, configFilename string

// Defaults assume a directory path
workingDir = componentPath
configFilename = config.DefaultTerragruntConfigPath

// If the path points directly to a file, split dir and filename
if util.FileExists(componentPath) && !util.IsDir(componentPath) {
workingDir = filepath.Dir(componentPath)
configFilename = filepath.Base(componentPath)
} else {
// Allow user-specified config filename when provided as a file path
if p := opts.TerragruntConfigPath; p != "" && !util.IsDir(p) {
configFilename = filepath.Base(p)
}
// Stacks always use the default stack filename
if c.Kind() == component.StackKind {
configFilename = config.DefaultStackFile
}
}

parseOpts.WorkingDir = workingDir

// Suppress logging to avoid cluttering the output.
parseOpts.Writer = io.Discard
parseOpts.ErrWriter = io.Discard
parseOpts.SkipOutput = true

// If the user provided a specific terragrunt config path and it is not a directory,
// use its base name as the file to parse. This allows users to run terragrunt with
// a specific config file instead of the default terragrunt.hcl.
// Otherwise, use the default terragrunt.hcl filename.
filename := config.DefaultTerragruntConfigPath
if opts.TerragruntConfigPath != "" && !util.IsDir(opts.TerragruntConfigPath) {
filename = filepath.Base(opts.TerragruntConfigPath)
}

// For stack configurations, always use the default stack config filename
if _, ok := c.(*component.Stack); ok {
filename = config.DefaultStackFile
}

parseOpts.TerragruntConfigPath = filepath.Join(parseOpts.WorkingDir, filename)
parseOpts.TerragruntConfigPath = filepath.Join(parseOpts.WorkingDir, configFilename)

parsingCtx := config.NewParsingContext(ctx, l, parseOpts).WithDecodeList(
config.TerraformSource,
config.DependenciesBlock,
config.DependencyBlock,
config.TerragruntFlags,
config.FeatureFlagsBlock,
config.ExcludeBlock,
config.ErrorsBlock,
).WithSkipOutputsResolution()

// Apply custom parser options if provided via discovery
Expand Down Expand Up @@ -505,10 +519,13 @@ func Parse(

// Set a list with partial blocks used to do discovery
parsingCtx = parsingCtx.WithDecodeList(
config.TerraformSource,
config.DependenciesBlock,
config.DependencyBlock,
config.TerragruntFlags,
config.FeatureFlagsBlock,
config.ExcludeBlock,
config.ErrorsBlock,
)

//nolint: contextcheck
Expand Down
26 changes: 20 additions & 6 deletions internal/runner/common/unit.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,7 @@ func (unit *Unit) FlushOutput() error {
}

if writer, ok := unit.TerragruntOptions.Writer.(*UnitWriter); ok {
key := unit.Path
if !filepath.IsAbs(key) {
if abs, err := filepath.Abs(key); err == nil {
key = abs
}
}
key := unit.AbsolutePath(unit.Logger)

mu := getUnitOutputLock(key)

Expand Down Expand Up @@ -166,6 +161,25 @@ func (unit *Unit) FindUnitInPath(targetDirs []string) bool {
return slices.Contains(targetDirs, unit.Path)
}

// AbsolutePath returns the absolute path of the unit.
// If path conversion fails, returns the original path and logs a warning.
func (unit *Unit) AbsolutePath(l log.Logger) string {
if filepath.IsAbs(unit.Path) {
return unit.Path
}

absPath, err := filepath.Abs(unit.Path)
if err != nil {
if l != nil {
l.Warnf("Failed to get absolute path for %s: %v", unit.Path, err)
}

return unit.Path
}

return absPath
}

// getDependenciesForUnit Get the list of units this unit depends on
func (unit *Unit) getDependenciesForUnit(unitsMap UnitsMap, terragruntConfigPaths []string) (Units, error) {
dependencies := Units{}
Expand Down
Loading
Loading