Skip to content

Conversation

@yhakbar
Copy link
Collaborator

@yhakbar yhakbar commented Oct 21, 2025

Description

Tracks reads in components, rather than tracking them in TerragruntOptions.

This is important, as it saves us from having shared mutable access to the map in TerragruntOptions, which should remove lock contention, and allows us to propagate the read files into the filter struct, which is useful as we filter on read files.

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

    • Internal refactoring of file-read tracking mechanisms across parsing and component discovery.
    • Removed deprecated file-read tracking API.
  • Tests

    • Added test coverage for file-read tracking validation.
    • Removed obsolete race condition test.

@vercel
Copy link

vercel bot commented Oct 21, 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 Oct 21, 2025 4:03pm

💡 Enable Vercel Agent with $100 free credit for automated AI reviews

@yhakbar yhakbar changed the title chore/tracking reads in components chore: Tracking reads in components Oct 21, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 21, 2025

📝 Walkthrough

Walkthrough

Replaces thread-safe map-based file read tracking in TerragruntOptions with slice-based tracking. Adds FilesRead field to ParsingContext, propagates read files to Reading field in Component and Unit structs during discovery and unit resolution, and removes the old AppendReadFile, DidReadFile, and CloneReadFiles APIs.

Changes

Cohort / File(s) Summary
File read tracking infrastructure
config/config_helpers.go, config/parsing_context.go
Config parsing now appends file paths directly to ctx.FilesRead instead of using AppendReadFile; new FilesRead field (pointer to slice) added to ParsingContext to centralize tracking.
Component and Unit model extensions
internal/component/component.go, internal/runner/common/unit.go
Added Reading field (slice of strings) to both Component and Unit structs to store paths of files read during parsing and execution.
Discovery layer integration
internal/discovery/discovery.go, internal/discovery/discovery_test.go
After parsing, component.Reading is populated from parseCtx.FilesRead; new test verifies Reading field is populated with files read via read_terragrunt_config and read_tfvars_file.
Unit resolver updates
internal/runner/common/unit_resolver.go, internal/runner/runnerpool/runner.go
Unit resolver extracts readFiles from parseCtx.FilesRead and stores in Unit.Reading; FilterDiscoveredUnits propagates Reading field when copying components; replaced DidReadFile checks with slices.Contains(unit.Reading, path).
Legacy tracking removal
options/options.go
Removed ReadFiles field (xsync.MapOf) from TerragruntOptions and its associated methods: AppendReadFile, DidReadFile, and CloneReadFiles.
Test cleanup
test/race_test.go
Deleted TestStrictModeWithRacing test that verified race conditions in the old AppendReadFile mechanism.

Sequence Diagram(s)

sequenceDiagram
    participant Parser as Config Parser
    participant ParsingCtx as ParsingContext
    participant Discovery as Discovery
    participant Component as Component
    participant UnitResolver as Unit Resolver
    participant Unit as Unit

    Parser->>ParsingCtx: Initialize with FilesRead slice
    Parser->>ParsingCtx: Append file paths to FilesRead during parsing
    
    Discovery->>ParsingCtx: Get FilesRead after parsing
    ParsingCtx-->>Discovery: Return []string of read files
    Discovery->>Component: Populate Reading field from FilesRead
    
    UnitResolver->>ParsingCtx: Extract FilesRead from parsing context
    ParsingCtx-->>UnitResolver: Return file paths
    UnitResolver->>Unit: Set Reading field with extracted paths
    
    Note over Unit: Contains all files read during<br/>parsing for this unit
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Rationale: Changes follow a consistent homogeneous pattern (adding Reading fields across multiple structs), but affect multiple layers of the system (parsing, discovery, unit resolution, runner). The architectural shift from thread-safe map-based tracking to slice-based propagation is straightforward but requires verification across integration points. Test coverage is added appropriately, and old race-condition test is removed as expected.

Possibly related PRs

Suggested reviewers

  • levkohimins
  • denis256

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The pull request description contains a clear and detailed explanation of the changes in the Description section, covering what is being changed and why. However, the description is incomplete in two critical areas required by the template. First, the Release Notes section contains only placeholder text "Added / Removed / Updated [X]" rather than an actual one-line description. Second, the Migration Guide section is empty, but this PR appears to be backward incompatible based on the removed public methods (AppendReadFile, DidReadFile, CloneReadFiles) from TerragruntOptions, which the template explicitly requires to be documented. The author should provide an actual one-line description for the Release Notes section (replacing the placeholder text) that summarizes the change in a format suitable for release documentation. Additionally, since this PR removes public methods from TerragruntOptions, a migration guide must be included to help users understand how to update their code to work with the new component-based read tracking approach instead of the removed TerragruntOptions methods.
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "chore: Tracking reads in components" directly and accurately reflects the main change described in the summary. The PR moves file read tracking from TerragruntOptions to components (including ParsingContext, Component, and Unit structures), and the title clearly captures this core objective. The title is concise, specific enough for a teammate scanning history to understand the primary change, and avoids vague terminology.
✨ 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 chore/tracking-reads-in-components

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

Base automatically changed from chore/tracking-dag-nodes-in-components to main October 21, 2025 16:01
@yhakbar yhakbar force-pushed the chore/tracking-reads-in-components branch from 4630c51 to 83b46c1 Compare October 21, 2025 16:03
@yhakbar yhakbar marked this pull request as ready for review October 21, 2025 16:52
@yhakbar yhakbar requested a review from denis256 as a code owner October 21, 2025 16:52
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: 0

🧹 Nitpick comments (2)
internal/runner/common/unit_resolver.go (1)

896-896: Consider a map for large Reading slices (optional optimization).

The current slices.Contains implementation is O(n) per unit checked. If unit.Reading or the number of units becomes large, consider using a map for O(1) lookups instead.

For example:

+func (r *UnitResolver) flagUnitsThatRead(opts *options.TerragruntOptions, units Units) Units {
+	if len(opts.UnitsReading) == 0 {
+		return units
+	}
+
+	// Build map of paths to check for O(1) lookup
+	pathsToCheck := make(map[string]bool, len(opts.UnitsReading))
+	for _, path := range opts.UnitsReading {
+		if !filepath.IsAbs(path) {
+			path = filepath.Join(opts.WorkingDir, path)
+			path = filepath.Clean(path)
+		}
+		pathsToCheck[path] = true
+	}
+
+	for _, unit := range units {
+		for _, readPath := range unit.Reading {
+			if pathsToCheck[readPath] {
+				unit.FlagExcluded = false
+				break
+			}
+		}
+	}
+
+	return units
+}
internal/discovery/discovery_test.go (1)

577-635: LGTM! Comprehensive test for Reading field population.

The test effectively verifies the entire flow: files are read during parsing → tracked in parsing context → propagated to component. Good coverage of read_terragrunt_config and read_tfvars_file.

Consider adding a test case for the mark_as_read function as well to ensure complete coverage of all read-tracking mechanisms:

// In terragrunt.hcl
locals {
  shared_config = read_terragrunt_config("../shared.hcl")
  tfvars = read_tfvars_file("../shared.tfvars")
  explicitly_marked = mark_as_read("../explicitly-tracked.txt")
}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2398a6f and 83b46c1.

📒 Files selected for processing (10)
  • config/config_helpers.go (4 hunks)
  • config/parsing_context.go (2 hunks)
  • internal/component/component.go (1 hunks)
  • internal/discovery/discovery.go (1 hunks)
  • internal/discovery/discovery_test.go (1 hunks)
  • internal/runner/common/unit.go (1 hunks)
  • internal/runner/common/unit_resolver.go (4 hunks)
  • internal/runner/runnerpool/runner.go (1 hunks)
  • options/options.go (0 hunks)
  • test/race_test.go (0 hunks)
💤 Files with no reviewable changes (2)
  • options/options.go
  • test/race_test.go
🧰 Additional context used
📓 Path-based instructions (1)
**/*.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:

  • internal/component/component.go
  • internal/runner/common/unit.go
  • config/config_helpers.go
  • internal/discovery/discovery_test.go
  • internal/discovery/discovery.go
  • config/parsing_context.go
  • internal/runner/runnerpool/runner.go
  • internal/runner/common/unit_resolver.go
🧬 Code graph analysis (3)
internal/discovery/discovery_test.go (4)
options/options.go (1)
  • NewTerragruntOptions (382-384)
test/helpers/logger/logger.go (1)
  • CreateLogger (9-14)
internal/discovery/discovery.go (1)
  • NewDiscovery (169-188)
internal/component/component.go (1)
  • Component (28-41)
config/parsing_context.go (2)
options/options.go (1)
  • TerragruntOptions (99-331)
config/config.go (1)
  • DefaultParserOptions (101-129)
internal/runner/common/unit_resolver.go (2)
internal/runner/common/unit.go (1)
  • Unit (24-33)
options/options.go (1)
  • TerragruntOptions (99-331)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Pull Request has non-contributor approval
  • GitHub Check: Pull Request has non-contributor approval
  • GitHub Check: Pull Request has non-contributor approval
  • GitHub Check: Pull Request has non-contributor approval
  • GitHub Check: Pull Request has non-contributor approval
  • GitHub Check: Pull Request has non-contributor approval
  • GitHub Check: Pull Request has non-contributor approval
  • GitHub Check: Pull Request has non-contributor approval
  • GitHub Check: Pull Request has non-contributor approval
  • GitHub Check: Pull Request has non-contributor approval
🔇 Additional comments (9)
internal/component/component.go (1)

35-36: LGTM! Clean addition of the Reading field.

The new Reading []string field is appropriately scoped as a public field for cross-package access and aligns with the PR's goal of tracking files read during component parsing.

internal/discovery/discovery.go (1)

496-500: Verify slice copy semantics.

The assignment c.Reading = *parsingCtx.FilesRead creates a copy of the slice header, but the underlying array is shared. If parsingCtx.FilesRead is appended to after this assignment, c.Reading won't see those additions (expected), but both slices share the same underlying array elements (modifications to existing elements would be visible to both).

Is this the intended behavior, or should this be a deep copy to ensure complete isolation?

internal/runner/common/unit.go (1)

28-28: LGTM! Consistent field addition.

The Reading field on Unit mirrors the same field on Component, maintaining consistency across the data structures.

config/parsing_context.go (2)

46-48: LGTM! Well-documented shared state design.

The pointer-to-slice approach is appropriate for sharing read files across parsing context copies. The comment clearly documents this design choice.


65-66: LGTM! Proper initialization.

The FilesRead field is correctly initialized in the constructor, ensuring it's never nil when methods append to it.

Also applies to: 71-71

internal/runner/runnerpool/runner.go (1)

515-515: LGTM! Reading field properly propagated.

The Reading field is correctly included when creating a filtered component copy, ensuring read-file information flows through the filtering process.

internal/runner/common/unit_resolver.go (2)

387-391: LGTM! Safe extraction of read files.

Proper nil check before dereferencing parseCtx.FilesRead. The slice assignment copies the slice header, capturing the state at this point.


414-414: LGTM! Reading field populated during unit creation.

The readFiles slice is correctly assigned to the new unit, enabling downstream filtering based on files read.

config/config_helpers.go (1)

723-724: No evidence of concurrent access - review comment appears speculative.

Thorough verification found no goroutines spawning with shared ParsingContext. All appends to FilesRead occur sequentially during single parse operations. Context copies via WithDecodeList(), WithParseOption(), etc. intentionally share the FilesRead pointer but are used in serial parsing flows. The slice reads in discovery.go and unit_resolver.go copy values rather than operate on the shared pointer.

While the lack of synchronization around slice appends is technically a vulnerability if concurrent access were introduced, the current codebase exhibits no race condition. The concern is valid as defensive programming but does not identify an actual bug in the current implementation.

@yhakbar yhakbar merged commit 3e7a615 into main Oct 21, 2025
119 of 121 checks passed
@yhakbar yhakbar deleted the chore/tracking-reads-in-components branch October 21, 2025 17:41
@coderabbitai coderabbitai bot mentioned this pull request Oct 22, 2025
6 tasks
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.

3 participants