Component · Apply
Reads the plan, builds an apply-specific graph, calls
ApplyResourceChange on each provider in dependency order,
and persists the resulting state.
ApplyCommand.Run() [command/apply.go:27]
│
├─► If plan file provided:
│ planfile.OpenWrapped(path) → *planfile.WrappedPlanFile
│ Contains: *plans.Plan, config snapshot, prior state
│
├─► PrepareBackend(planFile, args.State, args.ViewType) [apply.go:195]
│ Verifies backend config matches plan's embedded backend
│
├─► OperationRequest(be, view, …) [apply.go:244]
│ Attaches plan file to operation request
│
└─► be.RunOperation(ctx, opReq)
│
└─► (local backend) runOperationWithConfig()
│
└─► Context.Apply(plan, config, opts)
[context_apply.go:81]
│
├─► applyGraph(plan, config, opts, validate)
│ [context_apply.go:300]
│ Apply-specific graph builder:
│ NodeApplyableResourceInstance (create/update)
│ NodeDestroyResourceInstance (delete)
│ NodeApplyableOutput
│ NodeApplyableModuleCallOutput
│ ProviderTransformer (init/close lifecycle)
│ ReferenceTransformer
│ DestroyEdgeTransformer
│
├── Walk — Apply Phase (concurrent, ≤10 goroutines)
│ NodeApplyableResourceInstance.Execute():
│ 1. Look up ResourceInstanceChange in plan
│ 2. ctx.Provider(providerAddr) → providers.Interface
│ 3. providers.Interface.ApplyResourceChange(
│ PlannedState: change.After,
│ Config: evaluated config body,
│ PriorState: change.Before,
│ PlannedPrivate: change.Private)
│ → ApplyResourceChangeResponse{NewState, Private}
│ 4. SyncState.SetResourceInstanceCurrent(
│ addr, newObj, providerAddr)
│
├── NodeDestroyResourceInstance.Execute():
│ providers.Interface.ApplyResourceChange(
│ PlannedState: cty.NullVal, ← signals destroy
│ PriorState: current state)
│ SyncState.ForgetResourceInstanceAll(addr)
│
└─► statemgr.Full.WriteState(finalState)
Backend-specific persistence:
local: write terraform.tfstate JSON
remote: HTTP PUT to Terraform Cloud / S3 / etc.
internal/command/apply.go:19–100
GitHub
type ApplyCommand struct {
Meta
}
func (c *ApplyCommand) Run(rawArgs []string) int {
// Parse args; detect if a plan file was provided
args, diags := arguments.ParseApply(rawArgs)
// ...
// Try to open a plan file if the last arg looks like one
planFile, diags := c.LoadPlanFile(args.PlanPath)
// planFile may be nil if running without a saved plan
// Set up the backend
be, beDiags := c.PrepareBackend(planFile, args.State, args.ViewType)
// ...
opReq, opDiags := c.OperationRequest(be, view, args.ViewType,
planFile, args.Operation, args.AutoApprove)
// ...
op, err := c.RunOperation(be, opReq)
// ...
}
internal/terraform/context_apply.go:81–298
GitHub
// Apply applies the changes described in the plan to the real infrastructure.
// Returns the post-apply state.
func (c *Context) Apply(
plan *plans.Plan,
config *configs.Config,
opts *ApplyOpts,
) (*states.State, tfdiags.Diagnostics) {
state, _, diags := c.ApplyAndEval(plan, config, opts)
return state, diags
}
func (c *Context) ApplyAndEval(
plan *plans.Plan,
config *configs.Config,
opts *ApplyOpts,
) (*states.State, *lang.Scope, tfdiags.Diagnostics) {
// Build the apply graph
graph, operation, diags := c.applyGraph(plan, config, opts, true)
// ...
// Walk the graph — this is where provider calls happen
walker, walkDiags := c.walk(graph, operation, &graphWalkOpts{
Config: config,
InputState: plan.PriorState,
Changes: plan.Changes,
})
diags = diags.Append(walkDiags)
return walker.State.Close(), walker.EvalScope(nil, nil, nil), diags
}
internal/terraform/context_apply.go:300
GitHub
func (c *Context) applyGraph(
plan *plans.Plan,
config *configs.Config,
opts *ApplyOpts,
validate bool,
) (*Graph, walkOperation, tfdiags.Diagnostics) {
// Selects ApplyGraphBuilder or DestroyPlanGraphBuilder
// based on plan.UIMode
}
ApplyResourceChange then writes the response into SyncState.
ApplyResourceChange with a null proposed state, then removes the
instance from SyncState.
count / for_each
expansion by registering instance keys with the instances.Expander.
Create-before-destroy: When a resource requires replacement
(the plan contains Action: Replace), the apply graph inserts both
a create node and a destroy node with the create running first. The old instance
is temporarily stored in state as a "deposed" instance until the destroy completes.
After the graph walk completes, the final *states.State is flushed from
SyncState and written through the statemgr.Full interface.
The backend implementation decides how state is stored.
internal/states/statemgr/full.go — statemgr.Full interface
GitHub
// Full combines all state management operations.
type Full interface {
Reader
Writer
Refresher
Persister
Locker // optional — backends that support locking
}
type Writer interface {
WriteState(*states.State) error
}
type Persister interface {
PersistState(*schemas.Schemas) error
}
// Locker provides advisory locking to prevent concurrent applies.
type Locker interface {
Lock(info *LockInfo) (string, error)
Unlock(id string) error
}
terraform.tfstate (or the path from -state) as JSONPutObject to the configured bucket/key; DynamoDB for lockingstatemgr.Full
Partial failure safety: State is written after each successful
resource apply, not only at the end. If Terraform crashes mid-apply, the last-written
state still reflects all resources that were successfully applied, so a subsequent
terraform apply can pick up where it left off.
internal/backend/backend.go:44
GitHub
// Backend defines the interface for Terraform backends.
// Backends handle state storage and optionally remote operations.
type Backend interface {
// ConfigSchema returns the schema for the backend's configuration.
ConfigSchema() *configschema.Block
// PrepareConfig validates configuration before Configure is called.
PrepareConfig(cty.Value) (cty.Value, tfdiags.Diagnostics)
// Configure initializes the backend with the given configuration.
Configure(cty.Value) tfdiags.Diagnostics
// StateMgr returns a state manager for the given workspace.
StateMgr(workspace string) (statemgr.Full, tfdiags.Diagnostics)
// DeleteWorkspace removes the given workspace and its state.
DeleteWorkspace(name string, force bool) tfdiags.Diagnostics
// Workspaces returns the list of existing workspaces.
Workspaces() ([]string, tfdiags.Diagnostics)
}
// OperationsBackend extends Backend with support for remote plan/apply.
type OperationsBackend interface {
Backend
// RunOperation executes a plan or apply in the backend's context.
RunOperation(context.Context, *backendrun.Operation) (*backendrun.RunningOperation, error)
}