Open-Source Wikis

/

Terraform

/

Systems

/

State

hashicorp/terraform

State

Active contributors: James Bardin, Martin Atkins, Liam Cervante.

Purpose

internal/states/ is the in-memory representation of "what Terraform is currently managing." It is what the engine reads at the start of a plan or apply, and what it writes back at the end. The on-disk JSON format and its versioned readers/writers are in internal/states/statefile/. The pluggable persistence layer (local file, S3, GCS, Azure, …) is in internal/states/statemgr/.

Directory layout

internal/states/
├── state.go            # the State type and root-level operations
├── module.go           # Module — per-module-instance state container
├── resource.go         # Resource — per-resource block state container
├── instance_object.go  # ResourceInstanceObject — one materialized instance
├── instance_object_src.go  # ResourceInstanceObjectSrc — serialized form
├── output_value.go     # OutputValue
├── checks.go           # check-block result tracking
├── sync.go             # SyncState — concurrent-safe wrapper
├── state_deepcopy.go   # deep-copy for safe sharing
├── state_equal.go      # Equal()
├── state_string.go     # debug string form
├── doc.go
├── objectstatus_string.go  # generated stringer for object status
├── statefile/          # versioned on-disk JSON format
│   ├── version1.go, version2.go, version3.go, version4.go
│   ├── read.go, write.go
│   └── upgrade.go
├── statemgr/           # state-manager implementations
│   ├── filesystem.go   # local terraform.tfstate
│   ├── full.go         # the consolidated interface
│   ├── lock.go         # locking primitives
│   └── ...
└── remote/             # legacy remote-state support

Key types

Type File Description
states.State state.go The whole state. Root-level outputs and a map of addrs.ModuleInstance → *Module.
states.Module module.go Per-module-instance state. Has resources (keyed by addrs.Resource) and outputs.
states.Resource resource.go One resource block's instances. Provider config address + map of addrs.InstanceKey → *ResourceInstance.
states.ResourceInstance resource.go One instance. Has Current (a ResourceInstanceObjectSrc), Deposed (a map of deposed objects), and identity info.
states.ResourceInstanceObject instance_object.go A materialized instance — cty.Value plus status, dependencies, sensitive paths, identity.
states.ResourceInstanceObjectSrc instance_object_src.go The serialized form: AttrsJSON, AttrsFlat (legacy), schema version, dependencies, sensitivity.
states.SyncState sync.go Mutex-protected wrapper around *State. The thing the engine actually uses during walks.
statefile.File statefile/file.go Wraps a *State with version, lineage, and serial number.
statemgr.Full statemgr/full.go Consolidated interface (Reader+Writer+Persister+Refresher+Locker). All real state managers implement it.

How it works

graph TD
    Backend[backend.Backend.StateMgr] --> SM["statemgr.Full implementation<br/>(Filesystem, Remote, S3, GCS, K8s, ...)"]
    SM --> Read[ReadState → *statefile.File]
    Read --> StateObj[*states.State]
    Engine[terraform.Context] --> Sync[states.NewSyncState wraps *State]
    Engine -->|reads/writes via| Sync
    Sync -->|on success| Out[updated *states.State]
    Out --> Write[statemgr.WriteState]
    Write --> Persist[statemgr.PersistState — flush to durable storage]
    Persist --> SM

The status of an instance object

ObjectStatus (in state.go) has three values:

  • ObjectReady — the object is in good shape; reflects what the provider returned.
  • ObjectTainted — apply previously partially failed; the next plan should propose to replace it.
  • ObjectPlanned — set during apply between "we have a plan" and "the apply completed"; should never appear in a persisted state outside of a partial-apply scenario.

Deposed objects

create_before_destroy is implemented by deposing the old object instead of removing it from state. After a successful create, the new object becomes Current and the deposed object is destroyed. If the destroy fails, the deposed object remains in state under a key (addrs.DeposedKey) and the user can target it for cleanup. The data structures supporting this are Resource.Instances (current) and ResourceInstance.Deposed (a map keyed by DeposedKey).

Sensitivity

ResourceInstanceObjectSrc.AttrSensitivePaths records which sub-paths of the attribute JSON are sensitive. When the object is deserialized into a ResourceInstanceObject, those paths are re-marked with marks.Sensitive on the resulting cty.Value. This is how sensitive markings round-trip through state without leaking into JSON output.

Identity

The Identity field on ResourceInstanceObjectSrc (1.16+) carries a stable provider-assigned identifier separate from the address. It supports the new state identities subcommand (internal/command/state_identities.go) and lets providers report a server-side identifier even when the resource is moved between addresses.

Synchronization

states.SyncState (sync.go) wraps every read and write in a mutex. The engine's vertices use it via EvalContext.State(). Direct access to *states.State is reserved for code that runs serially (e.g. backend serialization).

The shape is:

ss := states.NewSyncState(state)
ss.SetResourceInstanceCurrent(addr, obj, providerCfg)
v := ss.ResourceInstance(addr)
final := ss.Close()  // returns the updated *states.State

Statefile format

internal/states/statefile/ implements a versioned JSON format. The current version is 4 — the same major version since 0.12. Version-specific readers (version1.go, version2.go, version3.go, version4.go) handle reading older files; writes always emit version 4.

Each statefile has:

  • version — integer.
  • terraform_version — string, for diagnostic messages.
  • serial — incremented every write. Used by remote managers to detect concurrent writes.
  • lineage — UUID. Used by remote managers to detect "different state on the other end."
  • outputs — root-level outputs.
  • resources — flat list of Resource entries, each with embedded Instances.
  • check_results — most recent check / postcondition results.

The upgrade.go machinery is invoked when reading: a version1.State is converted to v2, then v3, then v4, in order.

State managers

internal/states/statemgr/ defines several composable interfaces:

Interface Method Purpose
Reader State() *states.State Get current snapshot in memory.
Writer WriteState(*states.State) error Update in-memory snapshot (not flushed).
Refresher RefreshState() error Re-read from backing store.
Persister PersistState(schemarepo.Schemas) error Flush in-memory snapshot to backing store.
Locker Lock(LockInfo) (string, error), Unlock(string) error Cooperative locking.
OutputReader various Read outputs without loading the full state.
Migrator StateForMigration() (*statefile.File, error) Used by backend migrations to capture lineage/serial.

statemgr.Full combines them all; almost every concrete state manager implements Full.

The default statemgr.Filesystem (statemgr/filesystem.go) is the local-file-with-lock implementation that backs everything users see when they don't configure a backend. It writes to terraform.tfstate with a terraform.tfstate.lock.info lock file.

Integration points

  • Backends: every backend's StateMgr(workspace string) statemgr.Full produces a state manager. Configuring a workspace and reading its state is one call.
  • Engine: terraform.Context accepts a *states.State and produces an updated one via Plan (no change) or Apply (full update).
  • CLI: terraform state subcommands (list, mv, rm, pull, push, show, identities, replace-provider) operate on *states.State directly via the active state manager.
  • Plans: plans embed the prior state at planning time; the planfile carries it for round-trips.

Entry points for modification

  • Adding a new attribute to ResourceInstanceObjectSrc: add the field, update statefile read/write (version 4), update EncodeChange in internal/plans/changes_src.go if the value also lives in plans, and add tests covering serialization.
  • Adding a new state-file version: copy version4.go to version5.go, write the upgrader from v4→v5, and update the writer to emit v5. Be aware: the version is a durable contract — bumps require careful migration semantics.
  • Adding a new state manager: implement statemgr.Full and register a constructor in internal/backend/init/init.go.

For where state goes after Terraform updates it, see backends. For how state is read during evaluation, see terraform-core.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

State – Terraform wiki | Factory