Component · Configuration
HCL files are parsed into an in-memory module tree rooted at
*configs.Config. This tree feeds both the plan and apply engines.
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 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 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 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.
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)