Deprecated: Function get_magic_quotes_gpc() is deprecated in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 99

Deprecated: The each() function is deprecated. This message will be suppressed on further calls in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 619

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1169

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176
8000 fix: validate unique paths for includes and dependencies; validate unique block labels for all blocks. by pseudomorph · Pull Request #4706 · gruntwork-io/terragrunt · GitHub
Nothing Special   »   [go: up one dir, main page]

Skip to content

Conversation

pseudomorph
Copy link
Contributor
@pseudomorph pseudomorph commented Aug 19, 2025

Description

Fixes #4700

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 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:

  • Direct configuration parsing
  • Include file merging (shallow and deep merge)
  • All Terragrunt commands that parse configuration

No Action Required

If your configurations already follow best practices with unique paths and labels, no changes are needed.

Summary by CodeRabbit

  • New Features

    • Enforces unique include paths and unique dependency config_path values with clear, actionable errors.
    • Detects and blocks duplicate labeled blocks in configs before decoding.
  • Bug Fixes

    • Prevents ambiguous merges and parsing issues caused by duplicate include/dependency definitions.
  • Documentation

    • Clarified uniqueness requirements for include paths and dependency config_path.
    • Added include merge_strategy (no_merge, shallow, deep) guidance.
    • Documented new dependency mock_outputs_merge_strategy_with_state (and deprecation of mock_outputs_merge_with_state).
    • Clarified skip_outputs behavior and how mocks interact with state.

Copy link
vercel bot commented Aug 19, 2025

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

Project Deployment Preview Comments Updated (UTC)
terragrunt-docs Ready Ready Preview Comment Aug 19, 2025 6:03pm

Copy link
Contributor
coderabbitai bot commented Aug 19, 2025
📝 Walkthrough

Walkthrough

Adds 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

Cohort / File(s) Summary
Dependency uniqueness validation
config/dependency.go, config/include.go, config/config_partial.go
Add ValidateUniqueConfigPaths and wire into decode and include-merge paths; return errors on duplicate dependency config_path. Export MergeDependencyBlocks and use it at call sites. Add ValidateUniqueIncludePaths and call during base decode.
HCL parser: duplicate block detection
config/hclparse/file.go
Before decoding, validate no duplicate labeled blocks using hclsyntax; return diagnostic error on duplicates.
Unit tests: dependency uniqueness
config/dependency_test.go, config/include_test.go
Add tests covering success/failure for duplicate dependency config_path, including across include merges.
Unit tests: include uniqueness
config/config_partial_test.go
Add tests for ValidateUniqueIncludePaths across scenarios.
Unit tests: parser duplicate blocks
config/hclparse/file_test.go
Add tests verifying duplicate dependency block detection and non-syntax-body no-op behavior.
Docs: reference updates
docs-starlight/src/content/docs/04-reference/01-hcl/02-blocks.mdx
Document unique include path, unique dependency config_path, new include merge_strategy, and clarified dependency behaviors/attributes.

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
Loading
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
Loading
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Assessment against linked issues

Objective Addressed Explanation
Prevent duplicate dependency blocks by label (#4700)
Prevent multiple dependencies with the same config_path (#4700)
Prevent duplicate include blocks by label (#4700)
Prevent multiple include blocks with the same path (#4700)

Assessment against linked issues: Out-of-scope changes

(no out-of-scope functional code changes identified)

Suggested reviewers

  • wakeful
  • denis256

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 Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

Copy link
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: 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 path

Deep 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 paths

Use 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 values

FilteredWithoutConfigPath 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 + "." + labelsKey

And 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 package

The 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 dependencies

ValidateUniqueConfigPaths 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 check

Current 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 name

Update 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 phrasing

Minor 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 uniqueness

Minor 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 7a7494a and e27dc14.

📒 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 in docs-starlight. Make sure that the docs-starlight documentation is accurate and up-to-date with the docs documentation, and that any difference between them results in an improvement in the docs-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 blocks

Running 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 blocks

Good 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 covered

Verifies 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 validation

This 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 aggregation

Hooking ValidateUniqueIncludePaths into DecodeBaseBlocks and appending to errs preserves existing error-collection semantics. Looks solid.


466-470: Good: enforce dependency config_path uniqueness post-filter

Validating 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 testability

The 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 implementation

Calling out that each include must have a unique path matches the new validation logic.

Comment on lines +154 to +207
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)
})
}
Copy link
Contributor

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.

Suggested change
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.

Comment on lines +210 to +268
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)
})
}
}
Copy link
Contributor

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.

Comment on lines +237 to +241
// Validate that dependency config paths are unique
if err := ValidateUniqueConfigPaths(decodedDependency.Dependencies); err != nil {
return nil, err
}

Copy link
Contributor

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.

Comment on lines +212 to +217
83C6
mergedDependencyBlock := MergeDependencyBlocks(includedPartialParse.TerragruntDependencies, baseDependencyBlock)
// Validate that merged dependencies have unique config paths
if err := ValidateUniqueConfigPaths(mergedDependencyBlock); err != nil {
return nil, err
}

Copy link
Contributor

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.

Comment on lines 323 to 331
// 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

Copy link
Contributor

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.

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.

Duplicate dependency blocks and multiple dependencies with the same config path are allowed.

1 participant

12 0