9 version "github.com/hashicorp/go-version"
10 ctyjson "github.com/zclconf/go-cty/cty/json"
12 "github.com/hashicorp/terraform/addrs"
13 "github.com/hashicorp/terraform/states"
14 "github.com/hashicorp/terraform/tfdiags"
17 func readStateV4(src []byte) (*File, tfdiags.Diagnostics) {
18 var diags tfdiags.Diagnostics
20 err := json.Unmarshal(src, sV4)
22 diags = diags.Append(jsonUnmarshalDiags(err))
26 file, prepDiags := prepareStateV4(sV4)
27 diags = diags.Append(prepDiags)
31 func prepareStateV4(sV4 *stateV4) (*File, tfdiags.Diagnostics) {
32 var diags tfdiags.Diagnostics
34 var tfVersion *version.Version
35 if sV4.TerraformVersion != "" {
37 tfVersion, err = version.NewVersion(sV4.TerraformVersion)
39 diags = diags.Append(tfdiags.Sourceless(
41 "Invalid Terraform version string",
42 fmt.Sprintf("State file claims to have been written by Terraform version %q, which is not a valid version string.", sV4.TerraformVersion),
48 TerraformVersion: tfVersion,
53 state := states.NewState()
55 for _, rsV4 := range sV4.Resources {
56 rAddr := addrs.Resource{
62 rAddr.Mode = addrs.ManagedResourceMode
64 rAddr.Mode = addrs.DataResourceMode
66 diags = diags.Append(tfdiags.Sourceless(
68 "Invalid resource mode in state",
69 fmt.Sprintf("State contains a resource with mode %q (%q %q) which is not supported.", rsV4.Mode, rAddr.Type, rAddr.Name),
74 moduleAddr := addrs.RootModuleInstance
75 if rsV4.Module != "" {
76 var addrDiags tfdiags.Diagnostics
77 moduleAddr, addrDiags = addrs.ParseModuleInstanceStr(rsV4.Module)
78 diags = diags.Append(addrDiags)
79 if addrDiags.HasErrors() {
84 providerAddr, addrDiags := addrs.ParseAbsProviderConfigStr(rsV4.ProviderConfig)
85 diags.Append(addrDiags)
86 if addrDiags.HasErrors() {
90 var eachMode states.EachMode
91 switch rsV4.EachMode {
93 eachMode = states.NoEach
95 eachMode = states.EachList
97 eachMode = states.EachMap
99 diags = diags.Append(tfdiags.Sourceless(
101 "Invalid resource metadata in state",
102 fmt.Sprintf("Resource %s has invalid \"each\" value %q in state.", rAddr.Absolute(moduleAddr), eachMode),
107 ms := state.EnsureModule(moduleAddr)
109 // Ensure the resource container object is present in the state.
110 ms.SetResourceMeta(rAddr, eachMode, providerAddr)
112 for _, isV4 := range rsV4.Instances {
113 keyRaw := isV4.IndexKey
114 var key addrs.InstanceKey
115 switch tk := keyRaw.(type) {
117 key = addrs.IntKey(tk)
119 // Since JSON only has one number type, reading from encoding/json
120 // gives us a float64 here even if the number is whole.
121 // float64 has a smaller integer range than int, but in practice
122 // we rarely have more than a few tens of instances and so
123 // it's unlikely that we'll exhaust the 52 bits in a float64.
124 key = addrs.IntKey(int(tk))
126 key = addrs.StringKey(tk)
129 diags = diags.Append(tfdiags.Sourceless(
131 "Invalid resource instance metadata in state",
132 fmt.Sprintf("Resource %s has an instance with the invalid instance key %#v.", rAddr.Absolute(moduleAddr), keyRaw),
139 instAddr := rAddr.Instance(key)
141 obj := &states.ResourceInstanceObjectSrc{
142 SchemaVersion: isV4.SchemaVersion,
146 // Instance attributes
148 case isV4.AttributesRaw != nil:
149 obj.AttrsJSON = isV4.AttributesRaw
150 case isV4.AttributesFlat != nil:
151 obj.AttrsFlat = isV4.AttributesFlat
153 // This is odd, but we'll accept it and just treat the
154 // object has being empty. In practice this should arise
155 // only from the contrived sort of state objects we tend
156 // to hand-write inline in tests.
157 obj.AttrsJSON = []byte{'{', '}'}
166 obj.Status = states.ObjectReady
168 obj.Status = states.ObjectTainted
170 diags = diags.Append(tfdiags.Sourceless(
172 "Invalid resource instance metadata in state",
173 fmt.Sprintf("Instance %s has invalid status %q.", instAddr.Absolute(moduleAddr), raw),
179 if raw := isV4.PrivateRaw; len(raw) > 0 {
184 depsRaw := isV4.Dependencies
185 deps := make([]addrs.Referenceable, 0, len(depsRaw))
186 for _, depRaw := range depsRaw {
187 ref, refDiags := addrs.ParseRefStr(depRaw)
188 diags = diags.Append(refDiags)
189 if refDiags.HasErrors() {
192 if len(ref.Remaining) != 0 {
193 diags = diags.Append(tfdiags.Sourceless(
195 "Invalid resource instance metadata in state",
196 fmt.Sprintf("Instance %s declares dependency on %q, which is not a reference to a dependable object.", instAddr.Absolute(moduleAddr), depRaw),
199 if ref.Subject == nil {
200 // Should never happen
201 panic(fmt.Sprintf("parsing dependency %q for instance %s returned a nil address", depRaw, instAddr.Absolute(moduleAddr)))
203 deps = append(deps, ref.Subject)
205 obj.Dependencies = deps
209 case isV4.Deposed != "":
210 dk := states.DeposedKey(isV4.Deposed)
212 diags = diags.Append(tfdiags.Sourceless(
214 "Invalid resource instance metadata in state",
215 fmt.Sprintf("Instance %s has an object with deposed key %q, which is not correctly formatted.", instAddr.Absolute(moduleAddr), isV4.Deposed),
219 is := ms.ResourceInstance(instAddr)
220 if is.HasDeposed(dk) {
221 diags = diags.Append(tfdiags.Sourceless(
223 "Duplicate resource instance in state",
224 fmt.Sprintf("Instance %s deposed object %q appears multiple times in the state file.", instAddr.Absolute(moduleAddr), dk),
229 ms.SetResourceInstanceDeposed(instAddr, dk, obj, providerAddr)
231 is := ms.ResourceInstance(instAddr)
233 diags = diags.Append(tfdiags.Sourceless(
235 "Duplicate resource instance in state",
236 fmt.Sprintf("Instance %s appears multiple times in the state file.", instAddr.Absolute(moduleAddr)),
241 ms.SetResourceInstanceCurrent(instAddr, obj, providerAddr)
245 // We repeat this after creating the instances because
246 // SetResourceInstanceCurrent automatically resets this metadata based
247 // on the incoming objects. That behavior is useful when we're making
248 // piecemeal updates to the state during an apply, but when we're
249 // reading the state file we want to reflect its contents exactly.
250 ms.SetResourceMeta(rAddr, eachMode, providerAddr)
253 // The root module is special in that we persist its attributes and thus
254 // need to reload them now. (For descendent modules we just re-calculate
255 // them based on the latest configuration on each run.)
257 rootModule := state.RootModule()
258 for name, fos := range sV4.RootOutputs {
259 os := &states.OutputValue{}
260 os.Sensitive = fos.Sensitive
262 ty, err := ctyjson.UnmarshalType([]byte(fos.ValueTypeRaw))
264 diags = diags.Append(tfdiags.Sourceless(
266 "Invalid output value type in state",
267 fmt.Sprintf("The state file has an invalid type specification for output %q: %s.", name, err),
272 val, err := ctyjson.Unmarshal([]byte(fos.ValueRaw), ty)
274 diags = diags.Append(tfdiags.Sourceless(
276 "Invalid output value saved in state",
277 fmt.Sprintf("The state file has an invalid value for output %q: %s.", name, err),
283 rootModule.OutputValues[name] = os
291 func writeStateV4(file *File, w io.Writer) tfdiags.Diagnostics {
292 // Here we'll convert back from the "File" representation to our
293 // stateV4 struct representation and write that.
295 // While we support legacy state formats for reading, we only support the
296 // latest for writing and so if a V5 is added in future then this function
297 // should be deleted and replaced with a writeStateV5, even though the
298 // read/prepare V4 functions above would stick around.
300 var diags tfdiags.Diagnostics
301 if file == nil || file.State == nil {
302 panic("attempt to write nil state to file")
305 var terraformVersion string
306 if file.TerraformVersion != nil {
307 terraformVersion = file.TerraformVersion.String()
311 TerraformVersion: terraformVersion,
313 Lineage: file.Lineage,
314 RootOutputs: map[string]outputStateV4{},
315 Resources: []resourceStateV4{},
318 for name, os := range file.State.RootModule().OutputValues {
319 src, err := ctyjson.Marshal(os.Value, os.Value.Type())
321 diags = diags.Append(tfdiags.Sourceless(
323 "Failed to serialize output value in state",
324 fmt.Sprintf("An error occured while serializing output value %q: %s.", name, err),
329 typeSrc, err := ctyjson.MarshalType(os.Value.Type())
331 diags = diags.Append(tfdiags.Sourceless(
333 "Failed to serialize output value in state",
334 fmt.Sprintf("An error occured while serializing the type of output value %q: %s.", name, err),
339 sV4.RootOutputs[name] = outputStateV4{
340 Sensitive: os.Sensitive,
341 ValueRaw: json.RawMessage(src),
342 ValueTypeRaw: json.RawMessage(typeSrc),
346 for _, ms := range file.State.Modules {
347 moduleAddr := ms.Addr
348 for _, rs := range ms.Resources {
349 resourceAddr := rs.Addr
352 switch resourceAddr.Mode {
353 case addrs.ManagedResourceMode:
355 case addrs.DataResourceMode:
358 diags = diags.Append(tfdiags.Sourceless(
360 "Failed to serialize resource in state",
361 fmt.Sprintf("Resource %s has mode %s, which cannot be serialized in state", resourceAddr.Absolute(moduleAddr), resourceAddr.Mode),
370 case states.EachList:
375 diags = diags.Append(tfdiags.Sourceless(
377 "Failed to serialize resource in state",
378 fmt.Sprintf("Resource %s has \"each\" mode %s, which cannot be serialized in state", resourceAddr.Absolute(moduleAddr), rs.EachMode),
383 sV4.Resources = append(sV4.Resources, resourceStateV4{
384 Module: moduleAddr.String(),
386 Type: resourceAddr.Type,
387 Name: resourceAddr.Name,
389 ProviderConfig: rs.ProviderConfig.String(),
390 Instances: []instanceObjectStateV4{},
392 rsV4 := &(sV4.Resources[len(sV4.Resources)-1])
394 for key, is := range rs.Instances {
396 var objDiags tfdiags.Diagnostics
397 rsV4.Instances, objDiags = appendInstanceObjectStateV4(
398 rs, is, key, is.Current, states.NotDeposed,
401 diags = diags.Append(objDiags)
403 for dk, obj := range is.Deposed {
404 var objDiags tfdiags.Diagnostics
405 rsV4.Instances, objDiags = appendInstanceObjectStateV4(
406 rs, is, key, obj, dk,
409 diags = diags.Append(objDiags)
417 src, err := json.MarshalIndent(sV4, "", " ")
419 // Shouldn't happen if we do our conversion to *stateV4 correctly above.
420 diags = diags.Append(tfdiags.Sourceless(
422 "Failed to serialize state",
423 fmt.Sprintf("An error occured while serializing the state to save it. This is a bug in Terraform and should be reported: %s.", err),
427 src = append(src, '\n')
429 _, err = w.Write(src)
431 diags = diags.Append(tfdiags.Sourceless(
433 "Failed to write state",
434 fmt.Sprintf("An error occured while writing the serialized state: %s.", err),
442 func appendInstanceObjectStateV4(rs *states.Resource, is *states.ResourceInstance, key addrs.InstanceKey, obj *states.ResourceInstanceObjectSrc, deposed states.DeposedKey, isV4s []instanceObjectStateV4) ([]instanceObjectStateV4, tfdiags.Diagnostics) {
443 var diags tfdiags.Diagnostics
447 case states.ObjectReady:
449 case states.ObjectTainted:
452 diags = diags.Append(tfdiags.Sourceless(
454 "Failed to serialize resource instance in state",
455 fmt.Sprintf("Instance %s has status %s, which cannot be saved in state.", rs.Addr.Instance(key), obj.Status),
459 var privateRaw []byte
460 if len(obj.Private) > 0 {
461 privateRaw = obj.Private
464 deps := make([]string, len(obj.Dependencies))
465 for i, depAddr := range obj.Dependencies {
466 deps[i] = depAddr.String()
469 var rawKey interface{}
470 switch tk := key.(type) {
473 case addrs.StringKey:
476 if key != addrs.NoKey {
477 diags = diags.Append(tfdiags.Sourceless(
479 "Failed to serialize resource instance in state",
480 fmt.Sprintf("Instance %s has an unsupported instance key: %#v.", rs.Addr.Instance(key), key),
485 return append(isV4s, instanceObjectStateV4{
487 Deposed: string(deposed),
489 SchemaVersion: obj.SchemaVersion,
490 AttributesFlat: obj.AttrsFlat,
491 AttributesRaw: obj.AttrsJSON,
492 PrivateRaw: privateRaw,
497 type stateV4 struct {
498 Version stateVersionV4 `json:"version"`
499 TerraformVersion string `json:"terraform_version"`
500 Serial uint64 `json:"serial"`
501 Lineage string `json:"lineage"`
502 RootOutputs map[string]outputStateV4 `json:"outputs"`
503 Resources []resourceStateV4 `json:"resources"`
506 // normalize makes some in-place changes to normalize the way items are
507 // stored to ensure that two functionally-equivalent states will be stored
509 func (s *stateV4) normalize() {
510 sort.Stable(sortResourcesV4(s.Resources))
511 for _, rs := range s.Resources {
512 sort.Stable(sortInstancesV4(rs.Instances))
516 type outputStateV4 struct {
517 ValueRaw json.RawMessage `json:"value"`
518 ValueTypeRaw json.RawMessage `json:"type"`
519 Sensitive bool `json:"sensitive,omitempty"`
522 type resourceStateV4 struct {
523 Module string `json:"module,omitempty"`
524 Mode string `json:"mode"`
525 Type string `json:"type"`
526 Name string `json:"name"`
527 EachMode string `json:"each,omitempty"`
528 ProviderConfig string `json:"provider"`
529 Instances []instanceObjectStateV4 `json:"instances"`
532 type instanceObjectStateV4 struct {
533 IndexKey interface{} `json:"index_key,omitempty"`
534 Status string `json:"status,omitempty"`
535 Deposed string `json:"deposed,omitempty"`
537 SchemaVersion uint64 `json:"schema_version"`
538 AttributesRaw json.RawMessage `json:"attributes,omitempty"`
539 AttributesFlat map[string]string `json:"attributes_flat,omitempty"`
541 PrivateRaw []byte `json:"private,omitempty"`
543 Dependencies []string `json:"depends_on,omitempty"`
546 // stateVersionV4 is a weird special type we use to produce our hard-coded
547 // "version": 4 in the JSON serialization.
548 type stateVersionV4 struct{}
550 func (sv stateVersionV4) MarshalJSON() ([]byte, error) {
551 return []byte{'4'}, nil
554 func (sv stateVersionV4) UnmarshalJSON([]byte) error {
555 // Nothing to do: we already know we're version 4
559 type sortResourcesV4 []resourceStateV4
561 func (sr sortResourcesV4) Len() int { return len(sr) }
562 func (sr sortResourcesV4) Swap(i, j int) { sr[i], sr[j] = sr[j], sr[i] }
563 func (sr sortResourcesV4) Less(i, j int) bool {
565 case sr[i].Mode != sr[j].Mode:
566 return sr[i].Mode < sr[j].Mode
567 case sr[i].Type != sr[j].Type:
568 return sr[i].Type < sr[j].Type
569 case sr[i].Name != sr[j].Name:
570 return sr[i].Name < sr[j].Name
576 type sortInstancesV4 []instanceObjectStateV4
578 func (si sortInstancesV4) Len() int { return len(si) }
579 func (si sortInstancesV4) Swap(i, j int) { si[i], si[j] = si[j], si[i] }
580 func (si sortInstancesV4) Less(i, j int) bool {
584 if (ki == nil) != (kj == nil) {
587 if kii, isInt := ki.(int); isInt {
588 if kji, isInt := kj.(int); isInt {
593 if kis, isStr := ki.(string); isStr {
594 if kjs, isStr := kj.(string); isStr {
600 if si[i].Deposed != si[j].Deposed {
601 return si[i].Deposed < si[j].Deposed