Skip to content

feat: Terraform module composition edges + block-type labels (Resource/DataSource/Module/Variable/Output) #452

Description

@ilyabrykau-orca

What problem does this solve?

HCL is parsed and intra-file interpolation refs are captured, but two things are missing that make multi-module Terraform untraceable:

  1. No module composition edges. A module "x" { source = "../modules/y" } block is not linked to the module it instantiates.
  2. No block-type semantics. Every HCL block — resource, data, module, variable, output, provider, locals — collapses to the generic Class label, so they are indistinguishable and unfilterable.

Evidence from an indexed repo (1,431 .tf nodes):

  • .tf nodes by label: Class 1,147, File/Module 142 each. Every block is Class.
  • Intra-.tf edges: USAGE 995 (interpolation attribute refs ${aws_x.y.id} — good), DEFINES 1,289 (structural), but IMPORTS = 0 and CALLS = 0.
  • The Resource label that exists in the schema is populated by the K8s extractor (e.g. Job/...), not Terraform — so there is no Terraform-specific node typing at all.

Consequences:

  • "What instantiates the vpc module?" / "what stacks consume modules/sqs_queues?" — unanswerable.
  • Cannot filter by block kind (e.g. all data sources, all aws_* resources, all variables).
  • Cross-module/cross-stack blast radius is invisible despite the modules living in the same repo.

Proposed solution

In the HCL extraction path:

  1. Block-type labels instead of generic Class: TFResource, TFDataSource, TFModule, TFVariable, TFOutput, TFProvider, TFLocal (or a single label + block_type property). Keep the existing qualified-name scheme (...resource.aws_lambda_function.f).
  2. module.source -> IMPORTS edge from the module block to the resolved local module directory/Module node (relative source = "../modules/y" or "./modules/y"). External/registry sources (source = "terraform-aws-modules/vpc/aws") -> external module node, mirroring how external deps are represented elsewhere.
  3. depends_on -> explicit edge between the two blocks (distinct from interpolation USAGE).
  4. Resolve var.x / module.y.output / data.t.n references to the corresponding TFVariable / TFModule / TFDataSource nodes (extends the existing USAGE behaviour to module/var/output boundaries).

Public OSS test beds

  • terraform-aws-modules/terraform-aws-vpc and terraform-aws-modules/terraform-aws-eks — heavy nested-module source composition.
  • gruntwork-io/terragrunt-infrastructure-modules-example — multi-module/multi-stack wiring.
  • hashicorp/terraform-guides — mixed resource/data/module/variable/output blocks.

Alternatives considered

  • Keep generic Class + USAGE only (status quo): captures intra-file attribute refs but never module boundaries; multi-module repos look like disconnected islands.
  • Treat source as a string via search_code: grep-findable, no edge, so trace_path can't follow module composition.
  • External tooling (terraform graph): requires terraform init + provider creds and produces a separate artifact, not integrated into the code graph for cross-language queries.

Confirmations

  • I searched existing issues and this is not a duplicate.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestparsing/qualityGraph extraction bugs, false positives, missing edgespriority/normalStandard review queue; useful PR with ordinary maintainer urgency.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions