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 supportKey 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 --> SMThe 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.StateStatefile 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 ofResourceentries, each with embeddedInstances.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.Fullproduces a state manager. Configuring a workspace and reading its state is one call. - Engine:
terraform.Contextaccepts a*states.Stateand produces an updated one viaPlan(no change) orApply(full update). - CLI:
terraform statesubcommands (list, mv, rm, pull, push, show, identities, replace-provider) operate on*states.Statedirectly 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), updateEncodeChangeininternal/plans/changes_src.goif the value also lives in plans, and add tests covering serialization. - Adding a new state-file version: copy
version4.gotoversion5.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.Fulland register a constructor ininternal/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.