// Copyright IBM Corp. 2014, 2026 // SPDX-License-Identifier: BUSL-1.1 package statemgr import ( "context" "time" version "github.com/hashicorp/go-version" "github.com/hashicorp/terraform/internal/schemarepo" "github.com/hashicorp/terraform/internal/states" ) // Persistent is a union of the Refresher and Persistent interfaces, for types // that deal with persistent snapshots. // // Persistent snapshots are ones that are retained in storage that will // outlive a particular Terraform process, and are shared with other Terraform // processes that have a similarly-configured state manager. // // A manager may also choose to retain historical persistent snapshots, but // that is an implementation detail and not visible via this API. type Persistent interface { Refresher Persister OutputReader } // OutputReader is the interface for managers that fetches output values from state // or another source. This is a refinement of fetching the entire state and digging // the output values from it because operations backends can apply special permissions // to differentiate reading the state and reading the outputs within the state. type OutputReader interface { // GetRootOutputValues fetches the root module output values from state or another source GetRootOutputValues(ctx context.Context) (map[string]*states.OutputValue, error) } // Refresher is the interface for managers that can read snapshots from // persistent storage. // // Refresher is usually implemented in conjunction with Reader, with // RefreshState copying the latest persistent snapshot into the latest // transient snapshot. // // For a type that implements both Refresher and Persister, RefreshState must // return the result of the most recently completed successful call to // PersistState, unless another concurrently-running process has persisted // another snapshot in the mean time. // // The Refresher implementation must guarantee that the snapshot is read // from persistent storage in a way that is safe under concurrent calls to // PersistState that may be happening in other processes. type Refresher interface { // RefreshState retrieves a snapshot of state from persistent storage, // returning an error if this is not possible. // // Types that implement RefreshState generally also implement a State // method that returns the result of the latest successful refresh. // // Since only a subset of the data in a state is included when persisting, // a round-trip through PersistState and then RefreshState will often // return only a subset of what was written. Callers must assume that // ephemeral portions of the state may be unpopulated after calling // RefreshState. RefreshState() error } // Persister is the interface for managers that can write snapshots to // persistent storage. // // Persister is usually implemented in conjunction with Writer, with // PersistState copying the latest transient snapshot to be the new latest // persistent snapshot. // // A Persister implementation must detect updates made by other processes // that may be running concurrently and avoid destroying those changes. This // is most commonly achieved by making use of atomic write capabilities on // the remote storage backend in conjunction with book-keeping with the // Serial and Lineage fields in the standard state file formats. // // Some implementations may optionally utilize config schema to persist // state. For example, when representing state in an external JSON // representation. type Persister interface { PersistState(*schemarepo.Schemas) error } // PersistentMeta is an optional extension to Persistent that allows inspecting // the metadata associated with the snapshot that was most recently either // read by RefreshState or written by PersistState. type PersistentMeta interface { // StateSnapshotMeta returns metadata about the state snapshot most // recently created either by a call to PersistState or read by a call // to RefreshState. // // If no persistent snapshot is yet available in the manager then // the return value is meaningless. This method is primarily available // for testing and logging purposes, and is of little use otherwise. StateSnapshotMeta() SnapshotMeta } // SnapshotMeta contains metadata about a persisted state snapshot. // // This metadata is usually (but not necessarily) included as part of the // "header" of a state file, which is then written to a raw blob storage medium // by a persistent state manager. // // Not all state managers will have useful values for all fields in this // struct, so SnapshotMeta values are of little use beyond testing and logging // use-cases. type SnapshotMeta struct { // Lineage and Serial can be used to understand the relationships between // snapshots. // // If two snapshots both have an identical, non-empty Lineage // then the one with the higher Serial is newer than the other. // If the Lineage values are different or empty then the two snapshots // are unrelated and cannot be compared for relative age. Lineage string Serial uint64 // TerraformVersion is the number of the version of Terraform that created // the snapshot. TerraformVersion *version.Version } // IntermediateStateConditionalPersister is an optional extension of // [Persister] that allows an implementation to tailor the rules for // whether to create intermediate state snapshots when Terraform Core emits // events reporting that the state might have changed. This interface is used // by the local backend when it's been configured to use another backend for // state storage. // // For state managers that don't implement this interface, the local backend's // StateHook uses a default set of rules that aim to be a good compromise // between how long a state change can be active before it gets committed as a // snapshot vs. how many intermediate snapshots will get created. That // compromise is subject to change over time, but a state manager can implement // this interface to exert full control over those rules. type IntermediateStateConditionalPersister interface { // ShouldPersistIntermediateState will be called each time Terraform Core // emits an intermediate state event that is potentially eligible to be // persisted. // // The implemention should return true to signal that the state snapshot // most recently provided to the object's WriteState should be persisted, // or false if it should not be persisted. If this function returns true // then the receiver will see a subsequent call to // [statemgr.Persister.PersistState] to request persistence. // // The implementation must not modify anything reachable through the // arguments, and must not retain pointers to anything reachable through // them after the function returns. However, implementers can assume that // nothing will write to anything reachable through the arguments while // this function is active. ShouldPersistIntermediateState(info *IntermediateStatePersistInfo) bool } type IntermediateStatePersistInfo struct { // RequestedPersistInterval is the persist interval requested by whatever // instantiated the StateHook. // // Implementations of [IntermediateStateConditionalPersister] should ideally // respect this, but may ignore it if they use something other than the // passage of time to make their decision. RequestedPersistInterval time.Duration // LastPersist is the time when the last intermediate state snapshot was // persisted, or the time of the first report for Terraform Core if there // hasn't yet been a persisted snapshot. LastPersist time.Time // ForcePersist is true when Terraform CLI has receieved an interrupt // signal and is therefore trying to create snapshots more aggressively // in anticipation of possibly being terminated ungracefully. // [IntermediateStateConditionalPersister] implementations should ideally // persist every snapshot they get when this flag is set, unless they have // some external information that implies this shouldn't be necessary. ForcePersist bool } // DefaultIntermediateStatePersistRule is the default implementation of // [IntermediateStateConditionalPersister.ShouldPersistIntermediateState] used // when the selected state manager doesn't implement that interface. // // Implementers of that interface can optionally wrap a call to this function // if they want to combine the default behavior with some logic of their own. func DefaultIntermediateStatePersistRule(info *IntermediateStatePersistInfo) bool { return info.ForcePersist || time.Since(info.LastPersist) >= info.RequestedPersistInterval }