github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/states/statefile/version4.go (about) 1 package statefile 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "sort" 8 9 version "github.com/hashicorp/go-version" 10 ctyjson "github.com/zclconf/go-cty/cty/json" 11 12 "github.com/hashicorp/terraform/addrs" 13 "github.com/hashicorp/terraform/states" 14 "github.com/hashicorp/terraform/tfdiags" 15 ) 16 17 func readStateV4(src []byte) (*File, tfdiags.Diagnostics) { 18 var diags tfdiags.Diagnostics 19 sV4 := &stateV4{} 20 err := json.Unmarshal(src, sV4) 21 if err != nil { 22 diags = diags.Append(jsonUnmarshalDiags(err)) 23 return nil, diags 24 } 25 26 file, prepDiags := prepareStateV4(sV4) 27 diags = diags.Append(prepDiags) 28 return file, diags 29 } 30 31 func prepareStateV4(sV4 *stateV4) (*File, tfdiags.Diagnostics) { 32 var diags tfdiags.Diagnostics 33 34 var tfVersion *version.Version 35 if sV4.TerraformVersion != "" { 36 var err error 37 tfVersion, err = version.NewVersion(sV4.TerraformVersion) 38 if err != nil { 39 diags = diags.Append(tfdiags.Sourceless( 40 tfdiags.Error, 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), 43 )) 44 } 45 } 46 47 file := &File{ 48 TerraformVersion: tfVersion, 49 Serial: sV4.Serial, 50 Lineage: sV4.Lineage, 51 } 52 53 state := states.NewState() 54 55 for _, rsV4 := range sV4.Resources { 56 rAddr := addrs.Resource{ 57 Type: rsV4.Type, 58 Name: rsV4.Name, 59 } 60 switch rsV4.Mode { 61 case "managed": 62 rAddr.Mode = addrs.ManagedResourceMode 63 case "data": 64 rAddr.Mode = addrs.DataResourceMode 65 default: 66 diags = diags.Append(tfdiags.Sourceless( 67 tfdiags.Error, 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), 70 )) 71 continue 72 } 73 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() { 80 continue 81 } 82 } 83 84 providerAddr, addrDiags := addrs.ParseAbsProviderConfigStr(rsV4.ProviderConfig) 85 diags.Append(addrDiags) 86 if addrDiags.HasErrors() { 87 continue 88 } 89 90 var eachMode states.EachMode 91 switch rsV4.EachMode { 92 case "": 93 eachMode = states.NoEach 94 case "list": 95 eachMode = states.EachList 96 case "map": 97 eachMode = states.EachMap 98 default: 99 diags = diags.Append(tfdiags.Sourceless( 100 tfdiags.Error, 101 "Invalid resource metadata in state", 102 fmt.Sprintf("Resource %s has invalid \"each\" value %q in state.", rAddr.Absolute(moduleAddr), eachMode), 103 )) 104 continue 105 } 106 107 ms := state.EnsureModule(moduleAddr) 108 109 // Ensure the resource container object is present in the state. 110 ms.SetResourceMeta(rAddr, eachMode, providerAddr) 111 112 for _, isV4 := range rsV4.Instances { 113 keyRaw := isV4.IndexKey 114 var key addrs.InstanceKey 115 switch tk := keyRaw.(type) { 116 case int: 117 key = addrs.IntKey(tk) 118 case float64: 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)) 125 case string: 126 key = addrs.StringKey(tk) 127 default: 128 if keyRaw != nil { 129 diags = diags.Append(tfdiags.Sourceless( 130 tfdiags.Error, 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), 133 )) 134 continue 135 } 136 key = addrs.NoKey 137 } 138 139 instAddr := rAddr.Instance(key) 140 141 obj := &states.ResourceInstanceObjectSrc{ 142 SchemaVersion: isV4.SchemaVersion, 143 } 144 145 { 146 // Instance attributes 147 switch { 148 case isV4.AttributesRaw != nil: 149 obj.AttrsJSON = isV4.AttributesRaw 150 case isV4.AttributesFlat != nil: 151 obj.AttrsFlat = isV4.AttributesFlat 152 default: 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{'{', '}'} 158 } 159 } 160 161 { 162 // Status 163 raw := isV4.Status 164 switch raw { 165 case "": 166 obj.Status = states.ObjectReady 167 case "tainted": 168 obj.Status = states.ObjectTainted 169 default: 170 diags = diags.Append(tfdiags.Sourceless( 171 tfdiags.Error, 172 "Invalid resource instance metadata in state", 173 fmt.Sprintf("Instance %s has invalid status %q.", instAddr.Absolute(moduleAddr), raw), 174 )) 175 continue 176 } 177 } 178 179 if raw := isV4.PrivateRaw; len(raw) > 0 { 180 obj.Private = raw 181 } 182 183 { 184 // Allow both the deprecated `depends_on` and new 185 // `dependencies` to coexist for now so resources can be 186 // upgraded as they are refreshed. 187 depsRaw := isV4.DependsOn 188 deps := make([]addrs.Referenceable, 0, len(depsRaw)) 189 for _, depRaw := range depsRaw { 190 ref, refDiags := addrs.ParseRefStr(depRaw) 191 diags = diags.Append(refDiags) 192 if refDiags.HasErrors() { 193 continue 194 } 195 if len(ref.Remaining) != 0 { 196 diags = diags.Append(tfdiags.Sourceless( 197 tfdiags.Error, 198 "Invalid resource instance metadata in state", 199 fmt.Sprintf("Instance %s declares dependency on %q, which is not a reference to a dependable object.", instAddr.Absolute(moduleAddr), depRaw), 200 )) 201 } 202 if ref.Subject == nil { 203 // Should never happen 204 panic(fmt.Sprintf("parsing dependency %q for instance %s returned a nil address", depRaw, instAddr.Absolute(moduleAddr))) 205 } 206 deps = append(deps, ref.Subject) 207 } 208 obj.DependsOn = deps 209 } 210 211 { 212 depsRaw := isV4.Dependencies 213 deps := make([]addrs.AbsResource, 0, len(depsRaw)) 214 for _, depRaw := range depsRaw { 215 addr, addrDiags := addrs.ParseAbsResourceStr(depRaw) 216 diags = diags.Append(addrDiags) 217 if addrDiags.HasErrors() { 218 continue 219 } 220 deps = append(deps, addr) 221 } 222 obj.Dependencies = deps 223 } 224 225 switch { 226 case isV4.Deposed != "": 227 dk := states.DeposedKey(isV4.Deposed) 228 if len(dk) != 8 { 229 diags = diags.Append(tfdiags.Sourceless( 230 tfdiags.Error, 231 "Invalid resource instance metadata in state", 232 fmt.Sprintf("Instance %s has an object with deposed key %q, which is not correctly formatted.", instAddr.Absolute(moduleAddr), isV4.Deposed), 233 )) 234 continue 235 } 236 is := ms.ResourceInstance(instAddr) 237 if is.HasDeposed(dk) { 238 diags = diags.Append(tfdiags.Sourceless( 239 tfdiags.Error, 240 "Duplicate resource instance in state", 241 fmt.Sprintf("Instance %s deposed object %q appears multiple times in the state file.", instAddr.Absolute(moduleAddr), dk), 242 )) 243 continue 244 } 245 246 ms.SetResourceInstanceDeposed(instAddr, dk, obj, providerAddr) 247 default: 248 is := ms.ResourceInstance(instAddr) 249 if is.HasCurrent() { 250 diags = diags.Append(tfdiags.Sourceless( 251 tfdiags.Error, 252 "Duplicate resource instance in state", 253 fmt.Sprintf("Instance %s appears multiple times in the state file.", instAddr.Absolute(moduleAddr)), 254 )) 255 continue 256 } 257 258 ms.SetResourceInstanceCurrent(instAddr, obj, providerAddr) 259 } 260 } 261 262 // We repeat this after creating the instances because 263 // SetResourceInstanceCurrent automatically resets this metadata based 264 // on the incoming objects. That behavior is useful when we're making 265 // piecemeal updates to the state during an apply, but when we're 266 // reading the state file we want to reflect its contents exactly. 267 ms.SetResourceMeta(rAddr, eachMode, providerAddr) 268 } 269 270 // The root module is special in that we persist its attributes and thus 271 // need to reload them now. (For descendent modules we just re-calculate 272 // them based on the latest configuration on each run.) 273 { 274 rootModule := state.RootModule() 275 for name, fos := range sV4.RootOutputs { 276 os := &states.OutputValue{} 277 os.Sensitive = fos.Sensitive 278 279 ty, err := ctyjson.UnmarshalType([]byte(fos.ValueTypeRaw)) 280 if err != nil { 281 diags = diags.Append(tfdiags.Sourceless( 282 tfdiags.Error, 283 "Invalid output value type in state", 284 fmt.Sprintf("The state file has an invalid type specification for output %q: %s.", name, err), 285 )) 286 continue 287 } 288 289 val, err := ctyjson.Unmarshal([]byte(fos.ValueRaw), ty) 290 if err != nil { 291 diags = diags.Append(tfdiags.Sourceless( 292 tfdiags.Error, 293 "Invalid output value saved in state", 294 fmt.Sprintf("The state file has an invalid value for output %q: %s.", name, err), 295 )) 296 continue 297 } 298 299 os.Value = val 300 rootModule.OutputValues[name] = os 301 } 302 } 303 304 file.State = state 305 return file, diags 306 } 307 308 func writeStateV4(file *File, w io.Writer) tfdiags.Diagnostics { 309 // Here we'll convert back from the "File" representation to our 310 // stateV4 struct representation and write that. 311 // 312 // While we support legacy state formats for reading, we only support the 313 // latest for writing and so if a V5 is added in future then this function 314 // should be deleted and replaced with a writeStateV5, even though the 315 // read/prepare V4 functions above would stick around. 316 317 var diags tfdiags.Diagnostics 318 if file == nil || file.State == nil { 319 panic("attempt to write nil state to file") 320 } 321 322 var terraformVersion string 323 if file.TerraformVersion != nil { 324 terraformVersion = file.TerraformVersion.String() 325 } 326 327 sV4 := &stateV4{ 328 TerraformVersion: terraformVersion, 329 Serial: file.Serial, 330 Lineage: file.Lineage, 331 RootOutputs: map[string]outputStateV4{}, 332 Resources: []resourceStateV4{}, 333 } 334 335 for name, os := range file.State.RootModule().OutputValues { 336 src, err := ctyjson.Marshal(os.Value, os.Value.Type()) 337 if err != nil { 338 diags = diags.Append(tfdiags.Sourceless( 339 tfdiags.Error, 340 "Failed to serialize output value in state", 341 fmt.Sprintf("An error occured while serializing output value %q: %s.", name, err), 342 )) 343 continue 344 } 345 346 typeSrc, err := ctyjson.MarshalType(os.Value.Type()) 347 if err != nil { 348 diags = diags.Append(tfdiags.Sourceless( 349 tfdiags.Error, 350 "Failed to serialize output value in state", 351 fmt.Sprintf("An error occured while serializing the type of output value %q: %s.", name, err), 352 )) 353 continue 354 } 355 356 sV4.RootOutputs[name] = outputStateV4{ 357 Sensitive: os.Sensitive, 358 ValueRaw: json.RawMessage(src), 359 ValueTypeRaw: json.RawMessage(typeSrc), 360 } 361 } 362 363 for _, ms := range file.State.Modules { 364 moduleAddr := ms.Addr 365 for _, rs := range ms.Resources { 366 resourceAddr := rs.Addr 367 368 var mode string 369 switch resourceAddr.Mode { 370 case addrs.ManagedResourceMode: 371 mode = "managed" 372 case addrs.DataResourceMode: 373 mode = "data" 374 default: 375 diags = diags.Append(tfdiags.Sourceless( 376 tfdiags.Error, 377 "Failed to serialize resource in state", 378 fmt.Sprintf("Resource %s has mode %s, which cannot be serialized in state", resourceAddr.Absolute(moduleAddr), resourceAddr.Mode), 379 )) 380 continue 381 } 382 383 var eachMode string 384 switch rs.EachMode { 385 case states.NoEach: 386 eachMode = "" 387 case states.EachList: 388 eachMode = "list" 389 case states.EachMap: 390 eachMode = "map" 391 default: 392 diags = diags.Append(tfdiags.Sourceless( 393 tfdiags.Error, 394 "Failed to serialize resource in state", 395 fmt.Sprintf("Resource %s has \"each\" mode %s, which cannot be serialized in state", resourceAddr.Absolute(moduleAddr), rs.EachMode), 396 )) 397 continue 398 } 399 400 sV4.Resources = append(sV4.Resources, resourceStateV4{ 401 Module: moduleAddr.String(), 402 Mode: mode, 403 Type: resourceAddr.Type, 404 Name: resourceAddr.Name, 405 EachMode: eachMode, 406 ProviderConfig: rs.ProviderConfig.String(), 407 Instances: []instanceObjectStateV4{}, 408 }) 409 rsV4 := &(sV4.Resources[len(sV4.Resources)-1]) 410 411 for key, is := range rs.Instances { 412 if is.HasCurrent() { 413 var objDiags tfdiags.Diagnostics 414 rsV4.Instances, objDiags = appendInstanceObjectStateV4( 415 rs, is, key, is.Current, states.NotDeposed, 416 rsV4.Instances, 417 ) 418 diags = diags.Append(objDiags) 419 } 420 for dk, obj := range is.Deposed { 421 var objDiags tfdiags.Diagnostics 422 rsV4.Instances, objDiags = appendInstanceObjectStateV4( 423 rs, is, key, obj, dk, 424 rsV4.Instances, 425 ) 426 diags = diags.Append(objDiags) 427 } 428 } 429 } 430 } 431 432 sV4.normalize() 433 434 src, err := json.MarshalIndent(sV4, "", " ") 435 if err != nil { 436 // Shouldn't happen if we do our conversion to *stateV4 correctly above. 437 diags = diags.Append(tfdiags.Sourceless( 438 tfdiags.Error, 439 "Failed to serialize state", 440 fmt.Sprintf("An error occured while serializing the state to save it. This is a bug in Terraform and should be reported: %s.", err), 441 )) 442 return diags 443 } 444 src = append(src, '\n') 445 446 _, err = w.Write(src) 447 if err != nil { 448 diags = diags.Append(tfdiags.Sourceless( 449 tfdiags.Error, 450 "Failed to write state", 451 fmt.Sprintf("An error occured while writing the serialized state: %s.", err), 452 )) 453 return diags 454 } 455 456 return diags 457 } 458 459 func appendInstanceObjectStateV4(rs *states.Resource, is *states.ResourceInstance, key addrs.InstanceKey, obj *states.ResourceInstanceObjectSrc, deposed states.DeposedKey, isV4s []instanceObjectStateV4) ([]instanceObjectStateV4, tfdiags.Diagnostics) { 460 var diags tfdiags.Diagnostics 461 462 var status string 463 switch obj.Status { 464 case states.ObjectReady: 465 status = "" 466 case states.ObjectTainted: 467 status = "tainted" 468 default: 469 diags = diags.Append(tfdiags.Sourceless( 470 tfdiags.Error, 471 "Failed to serialize resource instance in state", 472 fmt.Sprintf("Instance %s has status %s, which cannot be saved in state.", rs.Addr.Instance(key), obj.Status), 473 )) 474 } 475 476 var privateRaw []byte 477 if len(obj.Private) > 0 { 478 privateRaw = obj.Private 479 } 480 481 deps := make([]string, len(obj.Dependencies)) 482 for i, depAddr := range obj.Dependencies { 483 deps[i] = depAddr.String() 484 } 485 486 depOn := make([]string, len(obj.DependsOn)) 487 for i, depAddr := range obj.DependsOn { 488 depOn[i] = depAddr.String() 489 } 490 491 var rawKey interface{} 492 switch tk := key.(type) { 493 case addrs.IntKey: 494 rawKey = int(tk) 495 case addrs.StringKey: 496 rawKey = string(tk) 497 default: 498 if key != addrs.NoKey { 499 diags = diags.Append(tfdiags.Sourceless( 500 tfdiags.Error, 501 "Failed to serialize resource instance in state", 502 fmt.Sprintf("Instance %s has an unsupported instance key: %#v.", rs.Addr.Instance(key), key), 503 )) 504 } 505 } 506 507 return append(isV4s, instanceObjectStateV4{ 508 IndexKey: rawKey, 509 Deposed: string(deposed), 510 Status: status, 511 SchemaVersion: obj.SchemaVersion, 512 AttributesFlat: obj.AttrsFlat, 513 AttributesRaw: obj.AttrsJSON, 514 PrivateRaw: privateRaw, 515 Dependencies: deps, 516 DependsOn: depOn, 517 }), diags 518 } 519 520 type stateV4 struct { 521 Version stateVersionV4 `json:"version"` 522 TerraformVersion string `json:"terraform_version"` 523 Serial uint64 `json:"serial"` 524 Lineage string `json:"lineage"` 525 RootOutputs map[string]outputStateV4 `json:"outputs"` 526 Resources []resourceStateV4 `json:"resources"` 527 } 528 529 // normalize makes some in-place changes to normalize the way items are 530 // stored to ensure that two functionally-equivalent states will be stored 531 // identically. 532 func (s *stateV4) normalize() { 533 sort.Stable(sortResourcesV4(s.Resources)) 534 for _, rs := range s.Resources { 535 sort.Stable(sortInstancesV4(rs.Instances)) 536 } 537 } 538 539 type outputStateV4 struct { 540 ValueRaw json.RawMessage `json:"value"` 541 ValueTypeRaw json.RawMessage `json:"type"` 542 Sensitive bool `json:"sensitive,omitempty"` 543 } 544 545 type resourceStateV4 struct { 546 Module string `json:"module,omitempty"` 547 Mode string `json:"mode"` 548 Type string `json:"type"` 549 Name string `json:"name"` 550 EachMode string `json:"each,omitempty"` 551 ProviderConfig string `json:"provider"` 552 Instances []instanceObjectStateV4 `json:"instances"` 553 } 554 555 type instanceObjectStateV4 struct { 556 IndexKey interface{} `json:"index_key,omitempty"` 557 Status string `json:"status,omitempty"` 558 Deposed string `json:"deposed,omitempty"` 559 560 SchemaVersion uint64 `json:"schema_version"` 561 AttributesRaw json.RawMessage `json:"attributes,omitempty"` 562 AttributesFlat map[string]string `json:"attributes_flat,omitempty"` 563 564 PrivateRaw []byte `json:"private,omitempty"` 565 566 Dependencies []string `json:"dependencies,omitempty"` 567 DependsOn []string `json:"depends_on,omitempty"` 568 } 569 570 // stateVersionV4 is a weird special type we use to produce our hard-coded 571 // "version": 4 in the JSON serialization. 572 type stateVersionV4 struct{} 573 574 func (sv stateVersionV4) MarshalJSON() ([]byte, error) { 575 return []byte{'4'}, nil 576 } 577 578 func (sv stateVersionV4) UnmarshalJSON([]byte) error { 579 // Nothing to do: we already know we're version 4 580 return nil 581 } 582 583 type sortResourcesV4 []resourceStateV4 584 585 func (sr sortResourcesV4) Len() int { return len(sr) } 586 func (sr sortResourcesV4) Swap(i, j int) { sr[i], sr[j] = sr[j], sr[i] } 587 func (sr sortResourcesV4) Less(i, j int) bool { 588 switch { 589 case sr[i].Mode != sr[j].Mode: 590 return sr[i].Mode < sr[j].Mode 591 case sr[i].Type != sr[j].Type: 592 return sr[i].Type < sr[j].Type 593 case sr[i].Name != sr[j].Name: 594 return sr[i].Name < sr[j].Name 595 default: 596 return false 597 } 598 } 599 600 type sortInstancesV4 []instanceObjectStateV4 601 602 func (si sortInstancesV4) Len() int { return len(si) } 603 func (si sortInstancesV4) Swap(i, j int) { si[i], si[j] = si[j], si[i] } 604 func (si sortInstancesV4) Less(i, j int) bool { 605 ki := si[i].IndexKey 606 kj := si[j].IndexKey 607 if ki != kj { 608 if (ki == nil) != (kj == nil) { 609 return ki == nil 610 } 611 if kii, isInt := ki.(int); isInt { 612 if kji, isInt := kj.(int); isInt { 613 return kii < kji 614 } 615 return true 616 } 617 if kis, isStr := ki.(string); isStr { 618 if kjs, isStr := kj.(string); isStr { 619 return kis < kjs 620 } 621 return true 622 } 623 } 624 if si[i].Deposed != si[j].Deposed { 625 return si[i].Deposed < si[j].Deposed 626 } 627 return false 628 }