| package statefile |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| |
| "github.com/hashicorp/terraform/internal/tfdiags" |
| ) |
| |
| func readStateV2(src []byte) (*File, tfdiags.Diagnostics) { |
| var diags tfdiags.Diagnostics |
| sV2 := &stateV2{} |
| err := json.Unmarshal(src, sV2) |
| if err != nil { |
| diags = diags.Append(jsonUnmarshalDiags(err)) |
| return nil, diags |
| } |
| |
| file, prepDiags := prepareStateV2(sV2) |
| diags = diags.Append(prepDiags) |
| return file, diags |
| } |
| |
| func prepareStateV2(sV2 *stateV2) (*File, tfdiags.Diagnostics) { |
| var diags tfdiags.Diagnostics |
| sV3, err := upgradeStateV2ToV3(sV2) |
| if err != nil { |
| diags = diags.Append(tfdiags.Sourceless( |
| tfdiags.Error, |
| upgradeFailed, |
| fmt.Sprintf("Error upgrading state file format from version 2 to version 3: %s.", err), |
| )) |
| return nil, diags |
| } |
| |
| file, prepDiags := prepareStateV3(sV3) |
| diags = diags.Append(prepDiags) |
| return file, diags |
| } |
| |
| // stateV2 is a representation of the legacy JSON state format version 2. |
| // |
| // It is only used to read version 2 JSON files prior to upgrading them to |
| // the current format. |
| type stateV2 struct { |
| // Version is the state file protocol version. |
| Version int `json:"version"` |
| |
| // TFVersion is the version of Terraform that wrote this state. |
| TFVersion string `json:"terraform_version,omitempty"` |
| |
| // Serial is incremented on any operation that modifies |
| // the State file. It is used to detect potentially conflicting |
| // updates. |
| Serial int64 `json:"serial"` |
| |
| // Lineage is set when a new, blank state is created and then |
| // never updated. This allows us to determine whether the serials |
| // of two states can be meaningfully compared. |
| // Apart from the guarantee that collisions between two lineages |
| // are very unlikely, this value is opaque and external callers |
| // should only compare lineage strings byte-for-byte for equality. |
| Lineage string `json:"lineage"` |
| |
| // Remote is used to track the metadata required to |
| // pull and push state files from a remote storage endpoint. |
| Remote *remoteStateV2 `json:"remote,omitempty"` |
| |
| // Backend tracks the configuration for the backend in use with |
| // this state. This is used to track any changes in the backend |
| // configuration. |
| Backend *backendStateV2 `json:"backend,omitempty"` |
| |
| // Modules contains all the modules in a breadth-first order |
| Modules []*moduleStateV2 `json:"modules"` |
| } |
| |
| type remoteStateV2 struct { |
| // Type controls the client we use for the remote state |
| Type string `json:"type"` |
| |
| // Config is used to store arbitrary configuration that |
| // is type specific |
| Config map[string]string `json:"config"` |
| } |
| |
| type outputStateV2 struct { |
| // Sensitive describes whether the output is considered sensitive, |
| // which may lead to masking the value on screen in some cases. |
| Sensitive bool `json:"sensitive"` |
| // Type describes the structure of Value. Valid values are "string", |
| // "map" and "list" |
| Type string `json:"type"` |
| // Value contains the value of the output, in the structure described |
| // by the Type field. |
| Value interface{} `json:"value"` |
| } |
| |
| type moduleStateV2 struct { |
| // Path is the import path from the root module. Modules imports are |
| // always disjoint, so the path represents amodule tree |
| Path []string `json:"path"` |
| |
| // Locals are kept only transiently in-memory, because we can always |
| // re-compute them. |
| Locals map[string]interface{} `json:"-"` |
| |
| // Outputs declared by the module and maintained for each module |
| // even though only the root module technically needs to be kept. |
| // This allows operators to inspect values at the boundaries. |
| Outputs map[string]*outputStateV2 `json:"outputs"` |
| |
| // Resources is a mapping of the logically named resource to |
| // the state of the resource. Each resource may actually have |
| // N instances underneath, although a user only needs to think |
| // about the 1:1 case. |
| Resources map[string]*resourceStateV2 `json:"resources"` |
| |
| // Dependencies are a list of things that this module relies on |
| // existing to remain intact. For example: an module may depend |
| // on a VPC ID given by an aws_vpc resource. |
| // |
| // Terraform uses this information to build valid destruction |
| // orders and to warn the user if they're destroying a module that |
| // another resource depends on. |
| // |
| // Things can be put into this list that may not be managed by |
| // Terraform. If Terraform doesn't find a matching ID in the |
| // overall state, then it assumes it isn't managed and doesn't |
| // worry about it. |
| Dependencies []string `json:"depends_on"` |
| } |
| |
| type resourceStateV2 struct { |
| // This is filled in and managed by Terraform, and is the resource |
| // type itself such as "mycloud_instance". If a resource provider sets |
| // this value, it won't be persisted. |
| Type string `json:"type"` |
| |
| // Dependencies are a list of things that this resource relies on |
| // existing to remain intact. For example: an AWS instance might |
| // depend on a subnet (which itself might depend on a VPC, and so |
| // on). |
| // |
| // Terraform uses this information to build valid destruction |
| // orders and to warn the user if they're destroying a resource that |
| // another resource depends on. |
| // |
| // Things can be put into this list that may not be managed by |
| // Terraform. If Terraform doesn't find a matching ID in the |
| // overall state, then it assumes it isn't managed and doesn't |
| // worry about it. |
| Dependencies []string `json:"depends_on"` |
| |
| // Primary is the current active instance for this resource. |
| // It can be replaced but only after a successful creation. |
| // This is the instances on which providers will act. |
| Primary *instanceStateV2 `json:"primary"` |
| |
| // Deposed is used in the mechanics of CreateBeforeDestroy: the existing |
| // Primary is Deposed to get it out of the way for the replacement Primary to |
| // be created by Apply. If the replacement Primary creates successfully, the |
| // Deposed instance is cleaned up. |
| // |
| // If there were problems creating the replacement Primary, the Deposed |
| // instance and the (now tainted) replacement Primary will be swapped so the |
| // tainted replacement will be cleaned up instead. |
| // |
| // An instance will remain in the Deposed list until it is successfully |
| // destroyed and purged. |
| Deposed []*instanceStateV2 `json:"deposed"` |
| |
| // Provider is used when a resource is connected to a provider with an alias. |
| // If this string is empty, the resource is connected to the default provider, |
| // e.g. "aws_instance" goes with the "aws" provider. |
| // If the resource block contained a "provider" key, that value will be set here. |
| Provider string `json:"provider"` |
| } |
| |
| type instanceStateV2 struct { |
| // A unique ID for this resource. This is opaque to Terraform |
| // and is only meant as a lookup mechanism for the providers. |
| ID string `json:"id"` |
| |
| // Attributes are basic information about the resource. Any keys here |
| // are accessible in variable format within Terraform configurations: |
| // ${resourcetype.name.attribute}. |
| Attributes map[string]string `json:"attributes"` |
| |
| // Meta is a simple K/V map that is persisted to the State but otherwise |
| // ignored by Terraform core. It's meant to be used for accounting by |
| // external client code. The value here must only contain Go primitives |
| // and collections. |
| Meta map[string]interface{} `json:"meta"` |
| |
| // Tainted is used to mark a resource for recreation. |
| Tainted bool `json:"tainted"` |
| } |
| |
| type backendStateV2 struct { |
| Type string `json:"type"` // Backend type |
| ConfigRaw json.RawMessage `json:"config"` // Backend raw config |
| Hash uint64 `json:"hash"` // Hash of portion of configuration from config files |
| } |