Skip to content

fix: follow local values when building the resource graph - #13

Open
ftgonc wants to merge 2 commits into
ops0-ai:mainfrom
ftgonc:fix/graph-locals-references
Open

ftgonc wants to merge 2 commits into
ops0-ai:mainfrom
ftgonc:fix/graph-locals-references

Conversation

@ftgonc

@ftgonc ftgonc commented Oct 14, 2026

Copy link
Copy Markdown

Summary

collect_references skips local.*, so any dependency that goes through a local value is lost. A resource with compartment_id = local.compartment_id, where the local reads a data source, gets no edge to that data source. Configs that route values through locals (very common) end up with a mostly disconnected resource graph: oxid graph under-reports dependencies, and plan/apply ordering misses real ones.

  • Keep local.<name> references, both in expressions and in ${…} interpolations inside nested blocks
  • resolve_local_references: resolve each local to the resource and data source addresses it reaches, following locals that reference other locals. Cycles between locals are cut instead of looping
  • Expand local references when adding edges for resources, data sources and outputs
  • Also collect references from count and for_each, not only from attributes (e.g. for_each = local.endpoints)
  • Keep resources whose count/for_each depends on locals, resources or data sources. The DAG builder evaluates these with variables only, so count = length(local.dbs) evaluated to 0 (and toset(local.ids) to an empty set) and the resource silently disappeared from the graph. Such resources are now a single unindexed node, as already happens when for_each evaluates to null; their dependencies are still tracked
  • Fix data source labels in to_dot, which were prefixed twice (data.data.x.y) because the address already includes data.

var.*, each.*, count.*, path.*, terraform.* and self are still ignored, as before.

Known gaps (not in this PR)

  • lifecycle { precondition / postcondition } blocks aren't parsed (LifecycleConfig has no field for them), so references inside them don't create edges. Terraform does count them
  • module.* references are still opaque
  • A resource whose count/for_each can't be evaluated at graph time is one unindexed node, so its instances aren't individually addressable in the graph. That's the existing behaviour for unresolvable for_each, now applied consistently; previously it had no node at all

Test plan

  • New tests/resource_graph_test.rs: local → data source (direct attribute and map attribute), chained locals through a for expression, for_each = local.x, output referencing a local, cyclic locals (no hang, no edges), var through a local (still no edge), count = length(local.x) and for_each = toset(local.x) keep the resource, count = var.n still expands, single data. prefix in DOT labels
  • Full test suite passes (129 tests, 0 failures)
  • cargo fmt --all -- --check clean
  • cargo clippy --all-targets -- -D warnings: no new findings. Current stable clippy reports 33 pre-existing double_must_use errors in src/state/backend.rs on main as well, untouched here
  • Manually on a real config that uses locals heavily (no modules): oxid graph went from 5 to 41 edges, and 34 of the 41 dependencies Terraform records in state are now reachable in the graph (previously almost none). At least one of the remaining 7 comes from a lifecycle precondition. On another config, 5 resources with count = length(local.…) that were missing from the graph now appear. No cycles were introduced on any of the six configurations tried

fg12345678 and others added 2 commits October 14, 2026 11:34
References through `local.*` were dropped when collecting dependencies,
so a resource using `compartment_id = local.compartment_id` (where the
local reads a data source or another resource) got no edge. For configs
that route values through locals, the graph was mostly disconnected:
`oxid graph` under-reported dependencies and plan/apply ordering missed
real ones.

- Keep `local.<name>` references (expressions and `${}` in nested blocks)
- Resolve each local to the resource and data source addresses it
  reaches, following locals that reference other locals; cycles between
  locals are cut instead of looping
- Expand local references for resource, data source and output edges
- Include references in `count` and `for_each`, not only attributes
- Fix data source DOT labels, which were prefixed twice (`data.data.`)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The DAG builder evaluates count and for_each with variables only. When
they depend on locals, resources or data sources (e.g.
`count = length(local.dbs)`), the expression evaluated against nulls:
length() returned 0 and toset() an empty set, so the resource got zero
instances and silently disappeared from the graph.

Treat such expressions as not statically known and keep the resource as
a single unindexed node, as is already done when for_each evaluates to
null. Its dependencies are still tracked through the count/for_each
references.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chaitanya-sistla

Copy link
Copy Markdown
Contributor

@ftgonc approved workflows

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