Skip to content

feat: draw module calls as nodes in oxid graph - #14

Open
ftgonc wants to merge 3 commits into
ops0-ai:mainfrom
ftgonc:feat/graph-module-nodes
Open

ftgonc wants to merge 3 commits into
ops0-ai:mainfrom
ftgonc:feat/graph-module-nodes

Conversation

@ftgonc

@ftgonc ftgonc commented Oct 14, 2026

Copy link
Copy Markdown

Stacked on #13 ("follow local values when building the resource graph"): this branch contains its two commits. Only the last commit, Draw module calls as nodes in oxid graph, is new here. I'll rebase once #13 is merged.

Summary

Modules are opaque to the resource graph, so oxid graph leaves out every module call. Configurations built mostly from modules render as a handful of disconnected data sources, and resources that consume module outputs have no incoming edges.

This adds module calls to the oxid graph output without expanding them:

  • Collect module.<name> references, both in expressions and in ${…} interpolations inside nested blocks. In the execution graph they still don't resolve to any node, so plan and apply are unchanged
  • New to_dot_with_modules, used by oxid graph: each module call becomes a node (shape=component) labelled with its name and a short form of its source (git::https://host/repo.git//database?ref=v1 → database; local and registry sources are shown as-is)
  • A module is linked from the resources, data sources and modules its arguments (including count, for_each and depends_on) reference, and to the resources, data sources, outputs and modules that use its outputs. Both directions follow locals, using the resolution from fix: follow local values when building the resource graph #13
  • Factor a resource's reference collection into resource_references, shared by the DAG builder and the DOT output

Out of scope

  • Expanding module contents (fetching sources, planning their resources). Module internals still don't appear, and plan/apply still treat modules as opaque
  • Module nodes in blast-radius or oxid graph -T module

Test plan

  • New tests in tests/resource_graph_test.rs: module node label and shape with a git source and a local source; edges data source → module (through a local), module → module (through a for over module instances), module → resource (via count and attributes through a local), module → output; to_dot (plan/apply graph) gains no module nodes; config without modules renders the same as to_dot
  • Full test suite passes (133 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 six real configurations: every module call now appears and is connected. For example, in a database config (12 nodes, 6 edges before) the database module now appears, linked from the network lookups it uses and to the five provisioning resources that consume its outputs (13 nodes, 14 edges); a config made only of modules went from 2 nodes and no edges to 5 nodes and 3 edges

fg12345678 and others added 3 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>
Modules are opaque to the resource graph, so `oxid graph` left out every
module call. Configurations built mostly from modules rendered as a few
disconnected data sources, and resources fed by module outputs had no
incoming edges.

- Collect `module.<name>` references (expressions and `${}` in nested
  blocks); they still don't resolve to nodes in the execution graph
- Add to_dot_with_modules, used by `oxid graph`: each module call is a
  node labelled with a short form of its source, linked from what its
  arguments reference and to the resources, data sources, outputs and
  modules that use its outputs, including through locals
- Factor resource reference collection into resource_references

Plan and apply are unchanged; module contents are still not expanded.

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

Copy link
Copy Markdown
Contributor

@ftgonc approved workflows

@chaitanya-sistla

Copy link
Copy Markdown
Contributor

@ftgonc most of the PRs security and clippy failed. Could you test this again?

@chaitanya-sistla

Copy link
Copy Markdown
Contributor

@ftgonc checking in here again.

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