Open-Source Wikis

/

Terraform

/

Systems

/

Plans

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 file

Key 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 -target or 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 in planproto/).
  • 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 .tf files).
  • 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, and cloud backends as either in-memory values (Operation.PlanFile) or pointers to a saved planfile.
  • CLI: internal/command/views/plan.go renders plans for users; internal/command/jsonplan/ and internal/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 to changes.go and changes_src.go, extend the protobuf in planproto/, regenerate via make protobuf, and update both readers and writers.
  • Adding a new Action value: extend action.go, regenerate the stringer (action_string.go), update every switch on Action (the compiler will help find them), and decide whether the new value needs a new ActionReason.
  • Adding a new PlanMode: extend mode.go, regenerate the stringer, audit terraform.Context and internal/command/arguments for 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.

Plans – Terraform wiki | Factory