Open-Source Wikis

/

Terraform

/

Systems

/

Terraform Core engine

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 configurations

Key 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:

  1. Picks a graph builder.
  2. Calls builder.Build() to get a *Graph.
  3. Wraps the result in a ContextGraphWalker.
  4. Calls dag.AcyclicGraph.Walk(walker.EnterPath, walker.Visit) on the graph.
  5. 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 []GraphTransformer

Steps() returns a list of transforms in the order they should run. Typical entries:

  • ConfigTransformer — one vertex per resource block.
  • StateTransformer — one vertex per resource instance currently in state.
  • ModuleVariableTransformer — one vertex per module input variable.
  • LocalTransformer — one vertex per locals declaration.
  • OutputTransformer — one vertex per output block.
  • ProvisionerTransformer — provisioner setup.
  • ProviderTransformer — resolves provider configs and inserts dependencies.
  • ReferenceTransformer — adds edges between vertices that reference each other.
  • ModuleExpansionTransformer — sets up dynamic expansion for count/for_each modules.
  • TargetsTransformer — prunes the graph to only -target ancestors.
  • 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, calls provider.PlanResourceChange, writes the planned change to ChangesSync.
  • NodeApplyableResourceInstance (node_resource_apply_instance.go): applies one planned change. Calls provider.ApplyResourceChange, updates SyncState.
  • 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 in node_provider.go: configure provider plugins before any resource calls them.
  • NodeLocal / NodeOutput: evaluate locals and outputs.
  • nodeExpandModule (node_module_expand.go): expands a module call with count/for_each into per-instance subgraphs.
  • The node_action_*.go family: 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.Interface lazily initialized by provider config vertices.
  • A *states.SyncState for all reads and writes.
  • A *plans.ChangesSync for 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.Scope factory 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 are node_resource_plan_instance.go, node_resource_apply_instance.go, and node_module_expand.go.
  • Transforms (transform_*.go) — start with transform_config.go, transform_reference.go, and transform_provider.go.
  • Walks (context_walk.go) — wraps the walker with operation-specific logging and error handling.
  • Conditions (eval_conditions.go) — precondition, postcondition, check blocks.
  • Variable validation (eval_variable.go, node_variable_validation.go) — variable validation rules.
  • Deferred actions (node_resource_apply_deferred.go and friends) — handling of partial plans when -target or unknown values prevent expansion.
  • Overrides (context_apply_overrides_test.go) — the test framework's mock_provider/override_resource machinery.

Integration points

  • Above: called from internal/backend/local/local.go (the local backend's Plan and Apply operations).
  • 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 via internal/plugin/internal/plugin6.

Entry points for modification

  • Adding a new top-level operation: add a Context method, a graph builder under graph_builder_<name>.go, possibly a new walk operation enum value, and the corresponding flow in the appropriate command.<...>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.go and dag.AcyclicGraph.Walk in internal/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.

Terraform Core engine – Terraform wiki | Factory