hashicorp/terraform
Terraform Core engine
Active contributors: Martin Atkins, James Bardin, Liam Cervante, Alisdair McDiarmid.
Purpose
internal/terraform/ is the brain. It takes a *configs.Config, a prior *states.State, and a set of variable values, and produces either a *plans.Plan (the result of a plan walk) or an updated *states.State (the result of an apply walk). Everything in the package is in service of that pipeline.
The package is large — 155 non-test Go files plus a testdata/ tree with 388 fixture directories. It is organized around three layers: the Context (per-operation god object), the graph builders (one per operation kind), and the vertex implementations (the node_*.go family).
Directory layout
internal/terraform/
├── context.go, context_*.go # terraform.Context — top-level orchestrator
├── graph.go, graph_builder.go # graph types and the GraphBuilder interface
├── graph_builder_plan.go # builds the plan graph
├── graph_builder_apply.go # builds the apply graph
├── graph_builder_init.go # builds the init graph
├── graph_builder_eval.go # builds the eval graph (for `terraform console`)
├── graph_walk.go, graph_walk_context.go # the walker driver
├── transform_*.go # ~40 graph transformers
├── node_*.go # ~50 vertex implementations
├── eval_context.go # the EvalContext interface
├── eval_context_builtin.go # the production EvalContext used during walks
├── eval_context_mock.go # the mock EvalContext used in tests
├── eval_*.go # per-feature evaluation helpers
├── evaluate.go # implements lang.EvalData on top of EvalContext
├── hook.go, hook_mock.go # progress hooks
├── upgrade_resource_state.go # state schema migration on read
├── reduce_plan.go # post-process the plan to drop unrelated entries
├── valuesourcetype_string.go etc. # generated stringers
├── testing/ # shared engine test helpers
└── testdata/ # 388 fixture configurationsKey types
| Type | File | Description |
|---|---|---|
terraform.Context |
context.go |
Per-operation coordinator. Owns the providers, the schema cache, the hook list, and produces the four public results: Plan, Apply, Refresh, Validate, plus Eval for terraform console. |
terraform.ContextOpts |
context.go |
Options to construct a Context: providers, provisioners, hooks, parallelism, plugin directories. |
terraform.GraphBuilder |
graph_builder.go |
Interface with one method, Build(addrs.Module) (*Graph, tfdiags.Diagnostics). Implemented per operation. |
terraform.Graph |
graph.go |
A wrapper around dag.AcyclicGraph with Terraform-specific helpers (path-aware indexing, debug utilities). |
terraform.GraphTransformer |
transform.go |
Interface with one method, Transform(*Graph) error. ~40 implementations live in transform_*.go. |
terraform.GraphNodeExecutable |
(interface in execute.go) |
Per-vertex Execute(EvalContext, walkOperation) method. About 30 vertex types implement this. |
terraform.GraphNodeDynamicExpandable |
graph_interface_subgraph.go |
Marker for vertices that produce a sub-graph at evaluation time (used for count/for_each, modules, providers). |
terraform.EvalContext |
eval_context.go |
The per-evaluation context. Provides access to providers, state, plan, scope, hook, and module-scoped data. |
terraform.BuiltinEvalContext |
eval_context_builtin.go |
The production EvalContext. Threadsafe; one per terraform.Context. |
terraform.ContextGraphWalker |
graph_walk_context.go |
The walker that maps dag walk steps onto Terraform's Execute semantics. |
How it works
graph TD
Inputs[*configs.Config + *states.State + variables] --> Ctx[terraform.Context]
Ctx --> Plan[Plan]
Plan --> PB[PlanGraphBuilder]
PB --> Transforms[apply transforms in order]
Transforms --> Graph[*Graph]
Graph --> Walker[ContextGraphWalker]
Walker --> Walk[dag.AcyclicGraph.Walk]
Walk --> Vertex[for each vertex: GraphNodeExecutable.Execute]
Vertex --> EvalCtx[BuiltinEvalContext]
EvalCtx --> Lang[lang.Scope.EvalExpr / EvalBlock]
EvalCtx --> Provider[providers.Interface — gRPC]
EvalCtx --> State[states.SyncState writes/reads]
EvalCtx --> Changes[plans.ChangesSync.AppendResourceInstanceChange]
Walk --> Result[*plans.Plan]terraform.Context
Constructed by the backend (local.Local) once it has all the inputs. The constructor (context.NewContext) initializes the schema cache, the function table, and the providers map. Each Plan, Apply, etc., method:
- Picks a graph builder.
- Calls
builder.Build()to get a*Graph. - Wraps the result in a
ContextGraphWalker. - Calls
dag.AcyclicGraph.Walk(walker.EnterPath, walker.Visit)on the graph. - Returns the accumulated plan / state / diagnostics.
The full plan/apply/etc. methods live in context_plan.go, context_apply.go, context_refresh.go, context_validate.go, context_input.go, context_eval.go, context_import.go, context_init.go. Each is hundreds of lines because it sets up the right Operation, picks the right walk operation enum (walkPlan, walkApply, walkPlanDestroy, walkValidate, walkRefresh, walkImport, walkEval), and synthesizes the right Plan/State objects to thread through.
Graph builders
Each builder is a sequence of transforms. The plan builder (graph_builder_plan.go) is representative:
&PlanGraphBuilder{
Config: config,
State: state,
RootVariableValues: vars,
Plugins: plugins,
Targets: targets,
Operation: walkPlan,
// ...
}.Steps() // returns []GraphTransformerSteps() returns a list of transforms in the order they should run. Typical entries:
ConfigTransformer— one vertex perresourceblock.StateTransformer— one vertex per resource instance currently in state.ModuleVariableTransformer— one vertex per module input variable.LocalTransformer— one vertex perlocalsdeclaration.OutputTransformer— one vertex peroutputblock.ProvisionerTransformer— provisioner setup.ProviderTransformer— resolves provider configs and inserts dependencies.ReferenceTransformer— adds edges between vertices that reference each other.ModuleExpansionTransformer— sets up dynamic expansion forcount/for_eachmodules.TargetsTransformer— prunes the graph to only-targetancestors.RootTransformer— adds the synthetic root vertex.TransitiveReductionTransformer— removes redundant edges.
Reading the Steps() method of each builder is the fastest way to understand what's special about that operation.
Vertex evaluation
Every vertex with non-trivial behavior implements GraphNodeExecutable. The walker calls Execute(EvalContext, walkOperation) once per vertex, in topological order, with parallelism bounded by edges and by the Parallelism setting in ContextOpts.
The most important vertex types:
NodePlannableResourceInstance(node_resource_plan_instance.go): plans one resource instance. Reads prior state, evaluates configuration, callsprovider.PlanResourceChange, writes the planned change toChangesSync.NodeApplyableResourceInstance(node_resource_apply_instance.go): applies one planned change. Callsprovider.ApplyResourceChange, updatesSyncState.NodeDestroyResourceInstance(node_resource_destroy.go): destroys one resource instance.NodePlannableResource(node_resource_plan.go): a resource block (the un-expanded form). Dynamically expands into per-instance vertices.nodeProviderConfig/ various provider node types innode_provider.go: configure provider plugins before any resource calls them.NodeLocal/NodeOutput: evaluate locals and outputs.nodeExpandModule(node_module_expand.go): expands amodulecall withcount/for_eachinto per-instance subgraphs.- The
node_action_*.gofamily: handles the new actions feature. nodeEphemeralResourceClose,node_resource_ephemeral.go: the ephemeral-resources feature.
EvalContext — the engine's global state for a walk
BuiltinEvalContext is a single object shared by all vertices in one walk. It holds:
- A
providers map[addrs.Provider]providers.Interfacelazily initialized by provider config vertices. - A
*states.SyncStatefor all reads and writes. - A
*plans.ChangesSyncfor the in-progress plan. - The function table from
lang/funcs/and a function-results cache. - The hook list (CLI views register here to render progress).
- A
lang.Scopefactory that produces a per-module scope.
Per-module data (variables, locals, outputs, expanded module instances) is keyed by addrs.ModuleInstance and resolved via the EvalContextScope helper (eval_context_scope.go).
Concurrency
The graph walker runs vertex evaluations in parallel up to Parallelism (default 10). All shared state goes through SyncState and ChangesSync, both of which use mutexes around their internal maps. Provider RPCs are synchronous within a vertex but concurrent across vertices; each provider plugin is goroutine-safe.
Hooks are called inside vertex evaluation and block the walker. CLI progress rendering must therefore be cheap.
Plan reduction
After a plan walk, reduce_plan.go removes changes that aren't user-meaningful (e.g. read-only data sources whose content didn't change but were re-read for freshness). This is the difference between the "raw" plan emitted by the engine and what the user actually sees.
State upgrades
When the engine reads a resource instance from prior state, the schema may have changed (the provider released a new version). upgrade_resource_state.go calls provider.UpgradeResourceState to migrate the stored value to the current schema before evaluation.
Sub-areas
The package is too large to cover in one page. Notable sub-areas worth reading independently:
- Vertex types (
node_*.go) — the most useful entry points arenode_resource_plan_instance.go,node_resource_apply_instance.go, andnode_module_expand.go. - Transforms (
transform_*.go) — start withtransform_config.go,transform_reference.go, andtransform_provider.go. - Walks (
context_walk.go) — wraps the walker with operation-specific logging and error handling. - Conditions (
eval_conditions.go) —precondition,postcondition,checkblocks. - Variable validation (
eval_variable.go,node_variable_validation.go) — variable validation rules. - Deferred actions (
node_resource_apply_deferred.goand friends) — handling of partial plans when-targetor unknown values prevent expansion. - Overrides (
context_apply_overrides_test.go) — the test framework'smock_provider/override_resourcemachinery.
Integration points
- Above: called from
internal/backend/local/local.go(the local backend'sPlanandApplyoperations). - Below: calls into
internal/configs(read-only),internal/states,internal/plans,internal/lang,internal/providers,internal/provisioners,internal/dag,internal/addrs. - Side: publishes events through
terraform.Hook; receives provider plugins viainternal/plugin/internal/plugin6.
Entry points for modification
- Adding a new top-level operation: add a
Contextmethod, a graph builder undergraph_builder_<name>.go, possibly a new walk operation enum value, and the corresponding flow in the appropriatecommand.<...>Command. - Adding a new vertex kind: create
node_<thing>.go, implement the relevant interfaces (GraphNodeExecutable,GraphNodeReferenceable,GraphNodeReferencer,GraphNodeProviderConsumer, …), and add a transformer that inserts it into the graph. - Changing how an existing vertex behaves: most "fixes" are in the relevant
node_*.go. Be aware: vertex methods are called from the walker at specific lifecycle points; a change in one method often ripples to its companion graph transform. - Tweaking parallelism / scheduling: see
context_walk.goanddag.AcyclicGraph.Walkininternal/dag/.
For the lower-level walk algorithm, see DAG primitive. For the value semantics, see language evaluation. For state and plan representations, see state and plans.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.