-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
fix: validate unique paths for includes and dependencies; validate unique block labels for all blocks. #4706
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
fix: validate unique paths for includes and dependencies; validate unique block labels for all blocks. #4706
Conversation
…ique block labels for all blocks.
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds validators to enforce uniqueness: dependency config_path values and include paths; introduces duplicate labeled-block detection during HCL decode; integrates these checks into decode/parse and include-merge flows; exports MergeDependencyBlocks; updates tests for new validations; updates docs to reflect uniqueness and merging semantics. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Parser
participant HCLFile
participant Decoder
Parser->>HCLFile: Decode()
HCLFile->>HCLFile: validateNoDuplicateBlocks()
alt duplicate labeled blocks
HCLFile-->>Parser: error (DiagError)
else no duplicates
HCLFile->>Decoder: proceed with decode
Decoder-->>Parser: decoded config
end
sequenceDiagram
autonumber
participant Loader as ConfigLoader
participant Decoder
participant Validator
Loader->>Decoder: Decode base config
Decoder->>Validator: ValidateUniqueIncludePaths(includes)
alt duplicate include path
Validator-->>Loader: error appended/returned
else ok
Validator-->>Loader: nil
Loader-->>Loader: continue processing
end
sequenceDiagram
autonumber
participant Merge as IncludeMerge
participant Merger as MergeDependencyBlocks
participant Validator
Merge->>Merger: Merge target+source dependencies
Merger-->>Merge: mergedDeps
Merge->>Validator: ValidateUniqueConfigPaths(mergedDeps)
alt duplicate config_path
Validator-->>Merge: error
else ok
Validator-->>Merge: nil
Merge-->>Merge: apply mergedDeps
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes(no out-of-scope functional code changes identified) Suggested reviewers
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
config/include.go (2)
220-228
: Add missing uniqueness validation for deep-merge pathDeep merges can also produce duplicate config_path across dependencies. Validate after deepMergeDependencyBlocks for parity with shallow merges.
case DeepMerge: l.Debugf("Included config %s has strategy deep merge: merging config in (deep) for dependency.", includeConfig.Path) mergedDependencyBlock, err := deepMergeDependencyBlocks(includedPartialParse.TerragruntDependencies, baseDependencyBlock) if err != nil { return nil, err } + // Validate that merged dependencies have unique config paths + if err := ValidateUniqueConfigPaths(Dependencies(mergedDependencyBlock)); err != nil { + return nil, err + } + baseDependencyBlock = mergedDependencyBlock
485-493
: Also validate deep-merged dependencies in DeepMerge()DeepMerge currently does not enforce unique config_path post-merge, unlike Merge(). Add the same validation here to prevent ambiguous configurations.
// Dependency blocks are deep merged by name mergedDeps, err := deepMergeDependencyBlocks(cfg.TerragruntDependencies, sourceConfig.TerragruntDependencies) if err != nil { return err } + // Validate that merged dependencies have unique config paths + if err := ValidateUniqueConfigPaths(Dependencies(mergedDeps)); err != nil { + return err + } + cfg.TerragruntDependencies = mergedDeps
🧹 Nitpick comments (9)
config/dependency.go (2)
122-145
: Prefer internal errors package over fmt.Errorf in validators; optionally normalize pathsUse the project's internal errors package to preserve stack traces. Also consider normalizing dependency paths (e.g., via a cleaned/absolute form) to catch semantically identical paths that differ textually ("../vpc" vs "./../vpc").
Apply this diff to switch to the internal errors package:
- return fmt.Errorf("duplicate config_path '%s' found in dependency blocks. "+ + return errors.Errorf("duplicate config_path '%s' found in dependency blocks. "+ "Dependency '%s' and dependency '%s' both point to the same config path. "+ "Each dependency must have a unique config_path", configPath, existingDepName, dep.Name)Optional follow-up (outside this hunk): if feasible, normalize configPath before comparison (e.g., using a cleaned path relative to the current config) to avoid false negatives when the same path is written differently. I can draft this with minimal API churn if you want to pass a base path into the validator.
1219-1229
: Filter should also drop empty string config_path valuesFilteredWithoutConfigPath currently only removes null cty values. An explicitly empty string slips through and then gets special-cased in ValidateUniqueConfigPaths. Tighten the filter here to exclude empty strings as well, keeping the pipeline consistent and reducing downstream branching.
- for _, dep := range deps { - if !dep.ConfigPath.IsNull() { - filteredDeps = append(filteredDeps, dep) - } - } + for _, dep := range deps { + if !dep.ConfigPath.IsNull() && dep.ConfigPath.AsString() != "" { + filteredDeps = append(filteredDeps, dep) + } + }config/hclparse/file.go (1)
139-172
: Use all labels for uniqueness and consider nested blocks (optional)The uniqueness key uses only the first label. For multi-label blocks, this can conflate distinct blocks or miss duplicates. Consider using the full label tuple for the key. Also, this currently checks only top-level blocks; if you intend to enforce uniqueness within nested contexts, recurse into child bodies.
Apply this diff to use the full label tuple for the key:
- // Check for duplicate block labels (only for blocks with labels) - if len(hclBlock.Labels) > 0 { - blockKey := hclBlock.Type + "." + hclBlock.Labels[0] + // Check for duplicate block labels (only for blocks with labels) + if len(hclBlock.Labels) > 0 { + labelsKey := strings.Join(hclBlock.Labels, ".") + blockKey := hclBlock.Type + "." + labelsKeyAnd add this import outside the selected hunk:
import "strings"Optional: If the requirement extends to nested blocks, we can introduce a small DFS over hclsyntax.Block.Body to apply the same uniqueness rule within each scope. I can provide an implementation if you want to expand the coverage.
config/config_partial_test.go (1)
392-446
: Tests look good; also ensure the include-path validator uses internal errors packageThe test suite correctly exercises duplicate include-path detection. For consistency with project practices (stack traces, etc.), ensure ValidateUniqueIncludePaths (in config/config_partial.go) uses the internal errors package rather than fmt.Errorf, similar to the suggestion for dependency config paths.
Proposed updated implementation (outside this file):
// In config/config_partial.go import "github.com/gruntwork-io/terragrunt/internal/errors" func ValidateUniqueIncludePaths(includes IncludeConfigs) error { includePaths := make(map[string]string) // map[path]include_name for _, inc := range includes { if inc.Path == "" { // Skip includes without path (shouldn't happen but be defensive) continue } if existingIncludeName, exists := includePaths[inc.Path]; exists { return errors.Errorf( "duplicate include path '%s' found in include blocks. "+ "Include '%s' and include '%s' both point to the same path. "+ "Each include must have a unique path", inc.Path, existingIncludeName, inc.Name, ) } includePaths[inc.Path] = inc.Name } return nil }Optional: as with dependency paths, consider normalizing include paths before comparison to catch semantically identical paths written differently.
config/dependency_test.go (1)
153-189
: Optional: add a test covering empty config_path dependenciesValidateUniqueConfigPaths skips empty config_path values (per implementation). A small positive test with a dependency lacking config_path would harden behavior guarantees.
I can draft a test case if you’d like.
config/config_partial.go (1)
760-781
: Consider normalizing include paths before uniqueness checkCurrent check treats "./parent.hcl" and "parent.hcl" as different. If practical, normalize (filepath.Clean, and optionally make absolute relative to the config dir) to catch semantically identical paths.
If you want to keep the current scope, no action is required for this PR.
config/include.go (1)
651-653
: Nit: comment references old function nameUpdate the comment to reflect the exported MergeDependencyBlocks name.
-// Merge dependency blocks deeply. This works almost the same as mergeDependencyBlocks, except it will recursively merge +// Merge dependency blocks deeply. This works almost the same as MergeDependencyBlocks, except it will recursively merge // attributes of the dependency struct if they share the same name.docs-starlight/src/content/docs/04-reference/01-hcl/02-blocks.mdx (2)
659-662
: Grammar: clarify deprecated bare-include phrasingMinor wording tweak for readability.
-You can have more than one `include` block, but each one must have a unique label and a unique path. It is recommended to -always label your `include` blocks. Bare includes (`include` block with no label - e.g., `include {}`) are currently -supported for backward compatibility, but is deprecated usage and support may be removed in the future. +You can have more than one `include` block, but each one must have a unique label and a unique path. It is recommended to +always label your `include` blocks. Bare includes (`include` block with no label — e.g., `include {}`) are currently +supported for backward compatibility, but this usage is deprecated and may be removed in the future.
1205-1218
: Tighten language around dependency labels and config_path uniquenessMinor style/grammar improvements.
-You can define more than one `dependency` block, but each one must have a unique label and a unique `config_path`. -Each label you provide to the block identifies another `dependency` -that you can reference in your config. +You can define more than one `dependency` block, but each one must have a unique label and a unique `config_path`. +Each label identifies a dependency that you can reference in your config.
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (9)
config/config_partial.go
(3 hunks)config/config_partial_test.go
(1 hunks)config/dependency.go
(2 hunks)config/dependency_test.go
(1 hunks)config/hclparse/file.go
(3 hunks)config/hclparse/file_test.go
(1 hunks)config/include.go
(3 hunks)config/include_test.go
(2 hunks)docs-starlight/src/content/docs/04-reference/01-hcl/02-blocks.mdx
(3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go
⚙️ CodeRabbit Configuration File
Review the Go code for quality and correctness. Make sure that the Go code follows best practices, is performant, and is easy to understand and maintain.
Files:
config/config_partial_test.go
config/hclparse/file.go
config/dependency.go
config/dependency_test.go
config/hclparse/file_test.go
config/include_test.go
config/config_partial.go
config/include.go
docs-starlight/**/*.md*
⚙️ CodeRabbit Configuration File
Review the documentation for clarity, grammar, and spelling. Make sure that the documentation is easy to understand and follow. There is currently a migration underway from the Jekyll based documentation in
docs
to the Starlight + Astro based documentation indocs-starlight
. Make sure that thedocs-starlight
documentation is accurate and up-to-date with thedocs
documentation, and that any difference between them results in an improvement in thedocs-starlight
documentation.
Files:
docs-starlight/src/content/docs/04-reference/01-hcl/02-blocks.mdx
🧠 Learnings (2)
📚 Learning: 2025-02-10T13:36:19.542Z
Learnt from: levkohimins
PR: gruntwork-io/terragrunt#3723
File: cli/commands/stack/action.go:160-160
Timestamp: 2025-02-10T13:36:19.542Z
Learning: The project uses a custom error package `github.com/gruntwork-io/terragrunt/internal/errors` which provides similar functionality to `fmt.Errorf` but includes stack traces. Prefer using this package's error functions (e.g., `errors.Errorf`, `errors.New`) over the standard library's error handling.
Applied to files:
config/include_test.go
📚 Learning: 2025-08-19T16:05:54.682Z
Learnt from: Resonance1584
PR: gruntwork-io/terragrunt#4683
File: go.mod:86-90
Timestamp: 2025-08-19T16:05:54.682Z
Learning: When analyzing Go module dependencies for removal, always check for both direct imports and API usage across all Go files in the repository, not just a quick search. The github.com/mattn/go-zglob library is used for filesystem walking and glob expansion in multiple Terragrunt files including util/file.go, format commands, and AWS provider patch functionality.
Applied to files:
config/include_test.go
🧬 Code Graph Analysis (7)
config/config_partial_test.go (2)
config/config.go (1)
IncludeConfigs
(748-748)config/config_partial.go (1)
ValidateUniqueIncludePaths
(761-781)
config/hclparse/file.go (1)
config/hclparse/block.go (1)
Block
(15-18)
config/dependency_test.go (4)
config/config.go (1)
DefaultTerragruntConfigPath
(47-47)config/hclparse/parser.go (1)
NewParser
(27-32)config/config_partial.go (1)
TerragruntDependency
(106-109)config/dependency.go (2)
ValidateUniqueConfigPaths
(123-144)Dependencies
(49-49)
config/hclparse/file_test.go (3)
config/hclparse/parser.go (1)
NewParser
(27-32)config/config_partial.go (1)
TerragruntDependency
(106-109)test/helpers/logger/logger.go (1)
CreateLogger
(9-14)
config/include_test.go (5)
config/config.go (1)
DefaultTerragruntConfigPath
(47-47)config/hclparse/parser.go (1)
NewParser
(27-32)config/config_partial.go (1)
TerragruntDependency
(106-109)config/include.go (1)
MergeDependencyBlocks
(622-649)config/dependency.go (2)
Dependencies
(49-49)ValidateUniqueConfigPaths
(123-144)
config/config_partial.go (2)
config/dependency.go (2)
ValidateUniqueConfigPaths
(123-144)Dependencies
(49-49)config/config.go (1)
IncludeConfigs
(748-748)
config/include.go (1)
config/dependency.go (2)
ValidateUniqueConfigPaths
(123-144)Dependency
(57-74)
🪛 LanguageTool
docs-starlight/src/content/docs/04-reference/01-hcl/02-blocks.mdx
[grammar] ~1206-~1206: There might be a mistake here.
Context: ...he block identifies another dependency
that you can reference in your config. ...
(QB_NEW_EN)
🔇 Additional comments (8)
config/hclparse/file.go (1)
69-73
: Good pre-decode guard for duplicate labeled blocksRunning duplicate-labeled-block validation before decoding is a solid safeguard that short-circuits ambiguous configs early.
config/hclparse/file_test.go (2)
14-91
: Solid coverage for duplicate-labeled blocksGood spread of cases (unique, single, empty, duplicate) and direct validation of the error substring. Parallelizing subtests is a nice touch.
92-104
: Non-syntax body case coveredVerifies graceful behavior when the body isn’t hclsyntax, preventing false positives. Looks good.
config/include_test.go (1)
424-496
: Good end-to-end test of include merge + duplicate-path validationThis verifies that MergeDependencyBlocks followed by ValidateUniqueConfigPaths catches duplicates and allows unique merges. Assertions are precise and parallelization is used correctly.
config/config_partial.go (2)
152-156
: Good: include-path uniqueness validated early with multi-error aggregationHooking ValidateUniqueIncludePaths into DecodeBaseBlocks and appending to errs preserves existing error-collection semantics. Looks solid.
466-470
: Good: enforce dependency config_path uniqueness post-filterValidating right after FilteredWithoutConfigPath() is the correct spot. This prevents later ambiguous merges/fetches.
config/include.go (1)
620-623
: LGTM: exporting MergeDependencyBlocks improves reuse and testabilityThe exported function name reads well and aligns with how it’s used across include merge paths.
docs-starlight/src/content/docs/04-reference/01-hcl/02-blocks.mdx (1)
669-669
: Docs align with implementationCalling out that each include must have a unique path matches the new validation logic.
func TestValidateUniqueConfigPaths_Success(t *testing.T) { | ||
t.Parallel() | ||
|
||
tests := []struct { | ||
name string | ||
hclCode string | ||
}{ | ||
{ | ||
name: "unique config paths", | ||
hclCode: ` | ||
dependency "vpc" { | ||
config_path = "../vpc" | ||
} | ||
dependency "database" { | ||
config_path = "../database" | ||
} | ||
`, | ||
}, | ||
{ | ||
name: "single dependency", | ||
hclCode: ` | ||
dependency "vpc" { | ||
config_path = "../vpc" | ||
} | ||
`, | ||
}, | ||
{ | ||
name: "no dependencies", | ||
hclCode: ` | ||
locals { | ||
vpc_id = "vpc-123" | ||
} | ||
`, | ||
}, | ||
} | ||
|
||
for _, test := range tests { | ||
t.Run(test.name, func(t *testing.T) { | ||
t.Parallel() | ||
|
||
filename := config.DefaultTerragruntConfigPath | ||
file, err := hclparse.NewParser().ParseFromString(test.hclCode, filename) | ||
require.NoError(t, err) | ||
|
||
decoded := config.TerragruntDependency{} | ||
err = file.Decode(&decoded, &hcl.EvalContext{}) | ||
require.NoError(t, err) | ||
|
||
// Also test the validation function directly | ||
err = config.ValidateUniqueConfigPaths(decoded.Dependencies) | ||
require.NoError(t, err) | ||
}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Fix parallel subtests capturing loop variable to avoid flakiness
t.Parallel is used inside subtests that close over the loop variable test. This can cause data races/flaky tests. Copy the loop variable before invoking t.Run.
Apply this diff:
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
+ for _, test := range tests {
+ tc := test
+ t.Run(tc.name, func(t *testing.T) {
t.Parallel()
filename := config.DefaultTerragruntConfigPath
- file, err := hclparse.NewParser().ParseFromString(test.hclCode, filename)
+ file, err := hclparse.NewParser().ParseFromString(tc.hclCode, filename)
require.NoError(t, err)
decoded := config.TerragruntDependency{}
- err = file.Decode(&decoded, &hcl.EvalContext{})
+ err = file.Decode(&decoded, &hcl.EvalContext{})
require.NoError(t, err)
// Also test the validation function directly
- err = config.ValidateUniqueConfigPaths(decoded.Dependencies)
+ err = config.ValidateUniqueConfigPaths(decoded.Dependencies)
require.NoError(t, err)
})
}
📝 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.
func TestValidateUniqueConfigPaths_Success(t *testing.T) { | |
t.Parallel() | |
tests := []struct { | |
name string | |
hclCode string | |
}{ | |
{ | |
name: "unique config paths", | |
hclCode: ` | |
dependency "vpc" { | |
config_path = "../vpc" | |
} | |
dependency "database" { | |
config_path = "../database" | |
} | |
`, | |
}, | |
{ | |
name: "single dependency", | |
hclCode: ` | |
dependency "vpc" { | |
config_path = "../vpc" | |
} | |
`, | |
}, | |
{ | |
name: "no dependencies", | |
hclCode: ` | |
locals { | |
vpc_id = "vpc-123" | |
} | |
`, | |
}, | |
} | |
for _, test := range tests { | |
t.Run(test.name, func(t *testing.T) { | |
t.Parallel() | |
filename := config.DefaultTerragruntConfigPath | |
file, err := hclparse.NewParser().ParseFromString(test.hclCode, filename) | |
require.NoError(t, err) | |
decoded := config.TerragruntDependency{} | |
err = file.Decode(&decoded, &hcl.EvalContext{}) | |
require.NoError(t, err) | |
// Also test the validation function directly | |
err = config.ValidateUniqueConfigPaths(decoded.Dependencies) | |
require.NoError(t, err) | |
}) | |
} | |
func TestValidateUniqueConfigPaths_Success(t *testing.T) { | |
t.Parallel() | |
tests := []struct { | |
name string | |
hclCode string | |
}{ | |
{ | |
name: "unique config paths", | |
hclCode: ` | |
dependency "vpc" { | |
config_path = "../vpc" | |
} | |
dependency "database" { | |
config_path = "../database" | |
} | |
`, | |
}, | |
{ | |
name: "single dependency", | |
hclCode: ` | |
dependency "vpc" { | |
config_path = "../vpc" | |
} | |
`, | |
}, | |
{ | |
name: "no dependencies", | |
hclCode: ` | |
locals { | |
vpc_id = "vpc-123" | |
} | |
`, | |
}, | |
} | |
for _, test := range tests { | |
tc := test | |
t.Run(tc.name, func(t *testing.T) { | |
t.Parallel() | |
filename := config.DefaultTerragruntConfigPath | |
file, err := hclparse.NewParser().ParseFromString(tc.hclCode, filename) | |
require.NoError(t, err) | |
decoded := config.TerragruntDependency{} | |
err = file.Decode(&decoded, &hcl.EvalContext{}) | |
require.NoError(t, err) | |
// Also test the validation function directly | |
err = config.ValidateUniqueConfigPaths(decoded.Dependencies) | |
require.NoError(t, err) | |
}) | |
} | |
} |
🤖 Prompt for AI Agents
In config/dependency_test.go around lines 154 to 207, the subtests use t.Run
with a closure that captures the loop variable `test`, causing potential
flakiness when running tests in parallel; to fix it, create a new local variable
inside the loop (e.g., `tc := test`) and use `tc` inside the t.Run closure (and
its inner t.Parallel call) so each goroutine gets its own copy of the test case,
preventing data races and nondeterministic behavior.
func TestValidateUniqueConfigPaths_Failure(t *testing.T) { | ||
t.Parallel() | ||
|
||
tests := []struct { | ||
name string | ||
hclCode string | ||
expectedErr string | ||
}{ | ||
{ | ||
name: "duplicate config paths", | ||
hclCode: ` | ||
dependency "vpc" { | ||
config_path = "../vpc" | ||
} | ||
dependency "network" { | ||
config_path = "../vpc" | ||
} | ||
`, | ||
expectedErr: "duplicate config_path '../vpc' found in dependency blocks. Dependency 'vpc' and dependency 'network' both point to the same config path", | ||
}, | ||
{ | ||
name: "multiple duplicates", | ||
hclCode: ` | ||
dependency "app1" { | ||
config_path = "../app" | ||
} | ||
dependency "app2" { | ||
config_path = "../app" | ||
} | ||
dependency "app3" { | ||
config_path = "../app" | ||
} | ||
`, | ||
expectedErr: "duplicate config_path '../app' found in dependency blocks. Dependency 'app1' and dependency 'app2' both point to the same config path", | ||
}, | ||
} | ||
|
||
for _, test := range tests { | ||
t.Run(test.name, func(t *testing.T) { | ||
t.Parallel() | ||
|
||
filename := config.DefaultTerragruntConfigPath | ||
file, err := hclparse.NewParser().ParseFromString(test.hclCode, filename) | ||
require.NoError(t, err) | ||
|
||
decoded := config.TerragruntDependency{} | ||
err = file.Decode(&decoded, &hcl.EvalContext{}) | ||
require.NoError(t, err) | ||
|
||
// Test the validation function directly | ||
err = config.ValidateUniqueConfigPaths(decoded.Dependencies) | ||
require.Error(t, err) | ||
assert.Contains(t, err.Error(), test.expectedErr) | ||
}) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Same loop-variable capture issue in failure tests
Mirror the fix here to prevent subtests from racing on test.
Apply this diff:
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
+ for _, test := range tests {
+ tc := test
+ t.Run(tc.name, func(t *testing.T) {
t.Parallel()
filename := config.DefaultTerragruntConfigPath
- file, err := hclparse.NewParser().ParseFromString(test.hclCode, filename)
+ file, err := hclparse.NewParser().ParseFromString(tc.hclCode, filename)
require.NoError(t, err)
decoded := config.TerragruntDependency{}
err = file.Decode(&decoded, &hcl.EvalContext{})
require.NoError(t, err)
// Test the validation function directly
err = config.ValidateUniqueConfigPaths(decoded.Dependencies)
require.Error(t, err)
- assert.Contains(t, err.Error(), test.expectedErr)
+ assert.Contains(t, err.Error(), tc.expectedErr)
})
}
🤖 Prompt for AI Agents
In config/dependency_test.go around lines 210 to 268, the failure tests use the
loop variable 'test' directly inside t.Run with t.Parallel, which causes a
loop-variable capture race between subtests; fix by copying the loop item to a
new local variable before calling t.Run (e.g., for i := range tests { tc :=
tests[i]; t.Run(tc.name, func(t *testing.T) { t.Parallel(); ... use tc ... }) })
so each subtest closes over its own copy.
// Validate that dependency config paths are unique | ||
if err := ValidateUniqueConfigPaths(decodedDependency.Dependencies); err != nil { | ||
return nil, err | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Validate duplicates after include-merging as well (verify wiring)
Great to validate before cycle detection. Ensure that duplicates introduced by include merging are also validated post-merge (the summary notes include.go does this). Let’s verify.
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Searching for post-merge validations in config/include.go..."
rg -nP -C3 'MergeDependencyBlocks|ValidateUniqueConfigPaths\s*\(' config/include.go || true
echo
echo "Other uses of ValidateUniqueConfigPaths in the codebase:"
rg -nP -C2 '\bValidateUniqueConfigPaths\s*\(' config | sed -n '1,200p'
Length of output: 5704
Ensure duplicate-path validation runs after every include-merge strategy
While we already call ValidateUniqueConfigPaths for shallow merges in config/include.go, the DeepMerge branches never re-validate post-merge. Please add duplicate-path checks in both DeepMerge sites:
• In the per-include DeepMerge case (around line 222 in config/include.go):
mergedDependencyBlock, err := deepMergeDependencyBlocks(...)
if err != nil {
return nil, err
}
+ // Validate that merged dependencies have unique config paths
+ if err := ValidateUniqueConfigPaths(mergedDependencyBlock); err != nil {
+ return nil, err
+ }
• In the final deep-merge at the bottom of config/include.go (around line 486):
mergedDeps, err := deepMergeDependencyBlocks(...)
if err != nil {
return err
}
+ // Validate that merged dependencies have unique config paths
+ if err := ValidateUniqueConfigPaths(mergedDeps); err != nil {
+ return err
+ }
Also extend config/include_test.go with a DeepMerge test to verify that duplicates are caught.
🤖 Prompt for AI Agents
In config/dependency.go around lines 237 to 241, add a call to
ValidateUniqueConfigPaths after any deep-merge of included configs so
duplicate-path validation runs post-merge (mirror the existing shallow-merge
check); also update config/include.go: insert ValidateUniqueConfigPaths call in
the per-include DeepMerge branch near line 222 and again after the final
deep-merge near line 486 to re-validate the merged result; finally add a unit
test in config/include_test.go that performs a DeepMerge scenario with duplicate
paths and asserts that ValidateUniqueConfigPaths returns an error.
mergedDependencyBlock := MergeDependencyBlocks(includedPartialParse.TerragruntDependencies, baseDependencyBlock) | ||
// Validate that merged dependencies have unique config paths | ||
if err := ValidateUniqueConfigPaths(mergedDependencyBlock); err != nil { | ||
return nil, err | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Type-safety: pass Dependencies to ValidateUniqueConfigPaths and keep validation
If Dependencies is a defined type (not an alias), passing []Dependency here will not compile. Cast to Dependencies explicitly to be safe.
- mergedDependencyBlock := MergeDependencyBlocks(includedPartialParse.TerragruntDependencies, baseDependencyBlock)
- // Validate that merged dependencies have unique config paths
- if err := ValidateUniqueConfigPaths(mergedDependencyBlock); err != nil {
+ mergedDependencyBlock := MergeDependencyBlocks(includedPartialParse.TerragruntDependencies, baseDependencyBlock)
+ // Validate that merged dependencies have unique config paths
+ if err := ValidateUniqueConfigPaths(Dependencies(mergedDependencyBlock)); err != nil {
return nil, err
}
🤖 Prompt for AI Agents
In config/include.go around lines 212 to 217, ValidateUniqueConfigPaths is being
called with mergedDependencyBlock as a []Dependency slice but the function
expects the Dependencies defined type; explicitly convert/cast
mergedDependencyBlock to Dependencies when calling ValidateUniqueConfigPaths
(e.g., ValidateUniqueConfigPaths(Dependencies(mergedDependencyBlock))) and keep
the existing validation branch and error return as-is so the code remains
type-safe and preserves the uniqueness check.
// Dependency blocks are shallow merged by name | ||
cfg.TerragruntDependencies = mergeDependencyBlocks(cfg.TerragruntDependencies, sourceConfig.TerragruntDependencies) | ||
mergedDeps := MergeDependencyBlocks(cfg.TerragruntDependencies, sourceConfig.TerragruntDependencies) | ||
// Validate that merged dependencies have unique config paths | ||
if err := ValidateUniqueConfigPaths(mergedDeps); err != nil { | ||
return err | ||
} | ||
|
||
cfg.TerragruntDependencies = mergedDeps | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Type-safety and parity: cast to Dependencies before validation
Same potential type mismatch here. Also, great to validate post-merge.
- mergedDeps := MergeDependencyBlocks(cfg.TerragruntDependencies, sourceConfig.TerragruntDependencies)
- // Validate that merged dependencies have unique config paths
- if err := ValidateUniqueConfigPaths(mergedDeps); err != nil {
+ mergedDeps := MergeDependencyBlocks(cfg.TerragruntDependencies, sourceConfig.TerragruntDependencies)
+ // Validate that merged dependencies have unique config paths
+ if err := ValidateUniqueConfigPaths(Dependencies(mergedDeps)); err != nil {
return err
}
cfg.TerragruntDependencies = mergedDeps
🤖 Prompt for AI Agents
In config/include.go around lines 323 to 331, after merging dependency blocks
the mergedDeps variable should be cast to the concrete Dependencies type before
validation to avoid type-mismatch issues; change the flow to convert mergedDeps
to Dependencies (e.g., deps := Dependencies(mergedDeps)) then call
ValidateUniqueConfigPaths(deps) and assign cfg.TerragruntDependencies = deps so
validation and assignment use the concrete, type-safe Dependencies value.
Description
Fixes #4700
TODOs
Read the Gruntwork contribution guidelines.
Release Notes (draft)
Added validation for unique dependency config_path values, unique include path values, and duplicate block labels to prevent configuration conflicts.
Migration Guide
Breaking Changes
This release adds stricter validation that may cause existing configurations to fail. The following validation errors may occur:
Duplicate Dependency Config Paths
Error:
duplicate config_path '../vpc' found in dependency blocks
Fix: Ensure each dependency block points to a unique config_path. If multiple dependencies were pointing to the same path, update them to point to different modules or consolidate into a single dependency with a more descriptive name.
Duplicate Include Paths
Error:
duplicate include path '../../terragrunt.hcl' found in include blocks
Fix: Ensure each include block points to a unique file path. If multiple includes were pointing to the same file, remove the duplicate includes or point them to different configuration files.
Duplicate Block Labels
Error:
Duplicate dependency block with label 'vpc' found
Fix: Ensure all blocks of the same type have unique labels. Rename duplicate labels to be more descriptive (e.g.,
vpc_primary
,vpc_secondary
).Validation Scope
These validations apply to:
No Action Required
If your configurations already follow best practices with unique paths and labels, no changes are needed.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation