hashicorp/terraform
Plans
Active contributors: James Bardin, Martin Atkins, Liam Cervante.
Purpose
internal/plans/ defines the plan: Terraform's description of changes proposed by plan and consumed by apply. A plan carries enough information to apply changes deterministically, render a human or JSON diff, and be persisted to disk for later use.
This package, like states, has both an in-memory model and a versioned persistence layer (internal/plans/planfile/).
Directory layout
internal/plans/
├── plan.go # the Plan type
├── changes.go # Changes — collection of resource instance changes
├── changes_src.go # serialized form of resource changes
├── changes_state.go # ChangesState — describes plan-time state side-effects
├── changes_sync.go # ChangesSync — concurrent wrapper used during planning
├── action.go # the Action enum (Create, Update, Delete, …)
├── action_invocation.go# ActionInvocation — for the new actions feature
├── action_string.go # generated stringer
├── mode.go, mode_string.go # PlanMode (Normal, Refresh, Destroy)
├── dynamic_value.go # DynamicValue — wire format for cty.Value in plans
├── deferring.go # ResourceInstanceChange.IsDeferred + DeferredReason
├── deferring/ # deferred-action helpers
├── quality.go # Plan.Quality — Complete | Errored | NoChanges
├── resourceinstancechangeactionreason_string.go # generated
├── objchange/ # diff/proposed-new-object utilities
├── planfile/ # on-disk plan file format (zip archive)
└── planproto/ # protobuf schema used inside the plan fileKey types
| Type | File | Description |
|---|---|---|
plans.Plan |
plan.go |
The full plan: changes, prior state, applied check results, variables, the cloud {} config, schema versions, etc. |
plans.Changes |
changes.go |
The collection of *ResourceInstanceChange. Has helpers like ResourceInstance(addr) and Empty(). |
plans.ResourceInstanceChange |
changes.go |
One proposed action on one resource instance: prior + planned cty.Value, action, action reason, dependencies, importing flag, RequiredReplace paths. |
plans.ResourceInstanceChangeSrc |
changes_src.go |
The serialized form (uses DynamicValue). Round-trips through the plan file. |
plans.ChangesSync |
changes_sync.go |
Mutex-protected wrapper around *Changes. Used during the plan walk. |
plans.Action |
action.go |
Enum: NoOp, Create, Read, Update, Delete, DeleteThenCreate, CreateThenDelete, Replace, Forget. |
plans.ActionReason |
changes.go |
Why the action was chosen — useful for error messages: Tainted, RequestedForceReplace, ReplaceTriggeredBy, DeleteBecauseNotInConfig, … |
plans.PlanMode |
mode.go |
NormalMode, RefreshOnlyMode, DestroyMode. |
plans.Quality |
quality.go |
High-level plan disposition. |
plans.DynamicValue |
dynamic_value.go |
Wire format: []byte containing the message-pack encoding of a cty.Value plus its type. |
plans.ActionInvocation |
action_invocation.go |
One invocation of an action {} block, with arguments and provider config. |
How it works
graph LR
Walk[plan walk: NodePlannableResourceInstance.Execute] --> CallPRC["provider.PlanResourceChange"]
CallPRC --> Diff[engine produces ResourceInstanceChange]
Diff --> Sync[ChangesSync.AppendResourceInstanceChange]
Sync --> Plan[Plan.Changes after walk completes]
Plan --> Render[CLI views render diff]
Plan --> Save[planfile.Create writes plan to disk]
Save --> Apply["terraform apply <planfile> reads it back"]The Plan struct
A *plans.Plan carries everything needed to faithfully replay an apply against the same providers and prior state. Notable fields:
Changes *Changes— the proposed actions.PriorState *states.State— the state as read at plan time.PrevRunState *states.State— the state as it was before refresh, useful for showing "drift detected" messages.Variables— the input variables resolved at plan time.VariableMarks— which variable values were sensitive/ephemeral.Targets,ForceReplace,Imports— the CLI flags that constrained planning.Backend— backend information so the apply can re-load the same state.UIMode plans.PlanMode— whether this is a normal plan, refresh-only, or destroy.Errored bool,Complete bool— correctness markers.Applyable bool— set when the plan can be applied (some refresh-only plans cannot).DeferredResources— entries deferred due to-targetor unknown values.Checks *states.CheckResults— pre/postcondition results from the plan walk.RelevantAttributes— used by-replace=…and friends to show only the relevant subset in diffs.Timestamp— when planning started.
Action and reason
The Action enum plus the ActionReason together explain what changed and why. This is how the renderer produces lines like:
# aws_instance.example is tainted, so must be replaced
-/+ resource "aws_instance" "example" { ... }Reasons cover dozens of cases: drift detection, -replace= flags, lifecycle.replace_triggered_by, removed blocks, import blocks, etc.
Dynamic values
DynamicValue is a typed-bytes wrapper around cty.Value:
type DynamicValue []byte
func NewDynamicValue(v cty.Value, ty cty.Type) (DynamicValue, error)
func (d DynamicValue) Decode(ty cty.Type) (cty.Value, error)The encoding is messagepack. Including the type means the decoder doesn't need provider schemas to round-trip a value; the engine still validates against the schema separately.
Deferred actions
A plan can declare some actions as "deferred" — Terraform knows it should take an action but cannot fully describe it because some inputs are unknown (typically due to -target or chained for_each over unknown). The DeferredResources list and the DeferredReason enum carry this information so a follow-up plan can resolve them.
The implementation lives in internal/plans/deferring/ and the engine integration is in the node_resource_apply_deferred.go family.
Plan files
internal/plans/planfile/ writes a plan to disk. The on-disk format is a zip archive containing:
tfplan— the protobuf-encoded plan (schema inplanproto/).tfstate— the prior state.terraform.tfstate— historical compatibility name; same as above.- The configuration as a tar archive (so
terraform apply <planfile>works without re-reading.tffiles). - The provider dependency lock file.
The file is opened with planfile.Open and read via planfile.Reader, which exposes typed accessors for each section.
objchange/
internal/plans/objchange/ is a small library used during planning to compute a proposed new object — the engine's first guess at what the resource should look like, given the prior state and the user's configuration, before the provider is consulted. The provider's PlanResourceChange then refines or replaces this guess. The package handles the subtle rules around computed attributes, defaults, and structural sharing.
It also holds AssertObjectCompatible, used to check that a provider's apply result is consistent with the planned change.
Integration points
- Engine: built up incrementally by vertices via
EvalContext.Changes()(which returns a*ChangesSync). Read by apply-side vertices. - Backends: plans are passed across the
local,remote, andcloudbackends as either in-memory values (Operation.PlanFile) or pointers to a saved planfile. - CLI:
internal/command/views/plan.gorenders plans for users;internal/command/jsonplan/andinternal/command/jsonformat/produce machine-readable forms. - State: plans embed prior state and produce updated state through apply.
Entry points for modification
- Adding a field to
ResourceInstanceChange: add it tochanges.goandchanges_src.go, extend the protobuf inplanproto/, regenerate viamake protobuf, and update both readers and writers. - Adding a new
Actionvalue: extendaction.go, regenerate the stringer (action_string.go), update every switch onAction(the compiler will help find them), and decide whether the new value needs a newActionReason. - Adding a new
PlanMode: extendmode.go, regenerate the stringer, auditterraform.Contextandinternal/command/argumentsfor handling. - Changing the plan-file format: bump the protobuf schema, write migration code, ensure the planfile reader can still load older files (used for plans saved during a previous version).
For how plans are produced, see terraform-core. For how they're rendered, see command package.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.