Component · Configuration

Configuration Loading

HCL files are parsed into an in-memory module tree rooted at *configs.Config. This tree feeds both the plan and apply engines.

Package: internal/configs
Configuration loading flow
configload.Loader.LoadConfig(rootDir)
  │
  ├─► Parser.LoadConfigDir(rootDir)
  │     ├── Glob *.tf and *.tf.json files in dir
  │     ├── hclsyntax.ParseConfig() for each .tf file (native syntax)
  │     ├── json.Parse() for each .tf.json file (JSON syntax)
  │     └── Merge body into *configs.Module
  │           Fields: Variables, Locals, Outputs, ManagedResources,
  │                   DataResources, ModuleCalls, ProviderConfigs,
  │                   RequiredProviders, Backend, CloudConfig,
  │                   Moved, Import, Checks, ...
  │
  └─► configs.BuildConfig(rootMod, walker, loader)  [config_build.go:30]
        │
        ├── Create root *Config node
        ├── For each rootMod.ModuleCalls:
        │     walker.LoadModule(call.SourceAddr, call.VersionConstraint)
        │       └── Returns child *configs.Module (from .terraform/modules/)
        │     Recurse: BuildConfig(childMod, walker, loader)
        │     Attach as root.Children[call.Name]
        │
        ├─► FinalizeConfig(cfg, walker, loader)  [config_build.go:46]
        │     ├── Resolve provider inheritance
        │     ├── Validate provider references
        │     └── Check for cycles in module tree
        │
        └─► *configs.Config  (full module tree, ready for planning)
configs.Config — module tree node

*configs.Config represents one node in the module call tree. The root node's Root field points to itself. Children maps each module block label to its own *Config subtree.

internal/configs/config.go:32 GitHub
type Config struct {
    // Root is the root node of the whole config tree.
    // (points to self for the root module)
    Root *Config

    // Parent is nil for the root module.
    Parent *Config

    // Path is the sequence of module calls to reach this node,
    // e.g. addrs.Module{"network", "vpc"} for module.network.module.vpc
    Path addrs.Module

    // Children maps the local module call name → child *Config.
    Children map[string]*Config

    // Module is the parsed contents of this directory's *.tf files.
    Module *Module

    // CallRange is the source range of the module {} block that
    // instantiated this node (empty for root).
    CallRange hcl.Range

    // SourceAddr is the parsed source address (registry, git, local).
    SourceAddr addrs.ModuleSource

    // Version is the exact version selected for registry modules.
    Version *version.Version
}
configs.Module — parsed HCL contents

*configs.Module holds everything parsed from a single module directory. The graph builder reads this to create nodes for each resource, variable, output, etc.

internal/configs/module.go (key fields) GitHub
type Module struct {
    // Identifiers
    SourceDir string

    // variable {} blocks  →  map[name]*Variable
    Variables map[string]*Variable

    // locals {} block  →  map[name]*Local
    Locals map[string]*Local

    // output {} blocks  →  map[name]*Output
    Outputs map[string]*Output

    // resource "type" "name" {}  →  map["type.name"]*ManagedResource
    ManagedResources map[string]*Resource

    // data "type" "name" {}  →  map["data.type.name"]*Resource
    DataResources map[string]*Resource

    // module "name" {}  →  map[name]*ModuleCall
    ModuleCalls map[string]*ModuleCall

    // provider "name" {}  →  map[name]*Provider
    ProviderConfigs map[string]*Provider

    // terraform { required_providers {} }
    ProviderRequirements map[addrs.Provider]providerreqs.Requirements

    // terraform { backend {} } or terraform { cloud {} }
    Backend     *Backend
    CloudConfig *CloudConfig

    // moved {}, import {}, check {} blocks
    Moved   []*Moved
    Import  []*Import
    Checks  map[string]*Check
}
BuildConfig — recursive module tree assembly

BuildConfig assembles the flat collection of parsed modules into a tree by following each module {} call recursively. The ModuleWalker interface abstracts how child modules are located (filesystem walk in production, mock in tests).

internal/configs/config_build.go:30 GitHub
// BuildConfig constructs a configs.Config from an already-loaded root
// module. The walker is called to load child modules on demand.
func BuildConfig(root *Module, walker ModuleWalker,
    loader MockDataLoader) (*Config, hcl.Diagnostics)

// FinalizeConfig resolves inter-module provider assignments.
func FinalizeConfig(cfg *Config, walker ModuleWalker,
    loader MockDataLoader) hcl.Diagnostics

// ModuleWalker is called for each module {} call encountered.
type ModuleWalker interface {
    LoadModule(req *ModuleRequest) (*Module, *version.Version, hcl.Diagnostics)
}

type ModuleRequest struct {
    Name               string              // module call label
    Path               addrs.Module        // path to the calling module
    SourceAddr         addrs.ModuleSource  // parsed source address
    VersionConstraint  VersionConstraint   // from version = "..."
    Parent             *Config
    CallRange          hcl.Range
}

Post-build: the returned *configs.Config is passed directly to Context.Plan() and Context.Apply(). The graph builder walks Config.Children recursively to create graph nodes for every resource across all modules.

Expression evaluation (Evaluator)

The Evaluator resolves HCL expressions at walk time, supplying values for variables, locals, resource attributes, and module outputs via the EvalContext interface. Unknown values (cty.DynamicVal) are propagated during plan when a value cannot be determined until apply.

internal/terraform/evaluate.go:32 GitHub
type Evaluator struct {
    Operation      walkOperation         // walkPlan, walkApply, etc.
    Meta           *ContextMeta
    Config         *configs.Config       // full module tree
    Instances      *instances.Expander   // track count/for_each expansions
    NamedValues    *namedvals.State      // var/local/output values
    EphemeralResources *ephemeral.Resources
    Deferrals      *deferring.Deferred
    Plugins        *contextPlugins       // available providers
    State          *states.SyncState     // current state
    Changes        *plans.ChangesSync    // planned changes so far
    PrevRunState   *states.SyncState     // state before refresh
    ExternalInputs cty.Value
}

// GetInputVariable resolves a variable reference like var.region.
// Returns cty.DynamicVal if the value is unknown at plan time.
func (e *Evaluator) GetInputVariable(addr addrs.InputVariable,
    module addrs.ModuleInstance) (cty.Value, tfdiags.Diagnostics)