github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/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 "github.com/zclconf/go-cty/cty" 11 ctyjson "github.com/zclconf/go-cty/cty/json" 12 13 "github.com/hashicorp/terraform/internal/addrs" 14 "github.com/hashicorp/terraform/internal/checks" 15 "github.com/hashicorp/terraform/internal/lang/marks" 16 "github.com/hashicorp/terraform/internal/states" 17 "github.com/hashicorp/terraform/internal/tfdiags" 18 ) 19 20 func readStateV4(src []byte) (*File, tfdiags.Diagnostics) { 21 var diags tfdiags.Diagnostics 22 sV4 := &stateV4{} 23 err := json.Unmarshal(src, sV4) 24 if err != nil { 25 diags = diags.Append(jsonUnmarshalDiags(err)) 26 return nil, diags 27 } 28 29 file, prepDiags := prepareStateV4(sV4) 30 diags = diags.Append(prepDiags) 31 return file, diags 32 } 33 34 func prepareStateV4(sV4 *stateV4) (*File, tfdiags.Diagnostics) { 35 var diags tfdiags.Diagnostics 36 37 var tfVersion *version.Version 38 if sV4.TerraformVersion != "" { 39 var err error 40 tfVersion, err = version.NewVersion(sV4.TerraformVersion) 41 if err != nil { 42 diags = diags.Append(tfdiags.Sourceless( 43 tfdiags.Error, 44 "Invalid Terraform version string", 45 fmt.Sprintf("State file claims to have been written by Terraform version %q, which is not a valid version string.", sV4.TerraformVersion), 46 )) 47 } 48 } 49 50 file := &File{ 51 TerraformVersion: tfVersion, 52 Serial: sV4.Serial, 53 Lineage: sV4.Lineage, 54 } 55 56 state := states.NewState() 57 58 for _, rsV4 := range sV4.Resources { 59 rAddr := addrs.Resource{ 60 Type: rsV4.Type, 61 Name: rsV4.Name, 62 } 63 switch rsV4.Mode { 64 case "managed": 65 rAddr.Mode = addrs.ManagedResourceMode 66 case "data": 67 rAddr.Mode = addrs.DataResourceMode 68 default: 69 diags = diags.Append(tfdiags.Sourceless( 70 tfdiags.Error, 71 "Invalid resource mode in state", 72 fmt.Sprintf("State contains a resource with mode %q (%q %q) which is not supported.", rsV4.Mode, rAddr.Type, rAddr.Name), 73 )) 74 continue 75 } 76 77 moduleAddr := addrs.RootModuleInstance 78 if rsV4.Module != "" { 79 var addrDiags tfdiags.Diagnostics 80 moduleAddr, addrDiags = addrs.ParseModuleInstanceStr(rsV4.Module) 81 diags = diags.Append(addrDiags) 82 if addrDiags.HasErrors() { 83 continue 84 } 85 } 86 87 providerAddr, addrDiags := addrs.ParseAbsProviderConfigStr(rsV4.ProviderConfig) 88 diags.Append(addrDiags) 89 if addrDiags.HasErrors() { 90 // If ParseAbsProviderConfigStr returns an error, the state may have 91 // been written before Provider FQNs were introduced and the 92 // AbsProviderConfig string format will need normalization. If so, 93 // we treat it like a legacy provider (namespace "-") and let the 94 // provider installer handle detecting the FQN. 95 var legacyAddrDiags tfdiags.Diagnostics 96 providerAddr, legacyAddrDiags = addrs.ParseLegacyAbsProviderConfigStr(rsV4.ProviderConfig) 97 if legacyAddrDiags.HasErrors() { 98 continue 99 } 100 } 101 102 ms := state.EnsureModule(moduleAddr) 103 104 // Ensure the resource container object is present in the state. 105 ms.SetResourceProvider(rAddr, providerAddr) 106 107 for _, isV4 := range rsV4.Instances { 108 keyRaw := isV4.IndexKey 109 var key addrs.InstanceKey 110 switch tk := keyRaw.(type) { 111 case int: 112 key = addrs.IntKey(tk) 113 case float64: 114 // Since JSON only has one number type, reading from encoding/json 115 // gives us a float64 here even if the number is whole. 116 // float64 has a smaller integer range than int, but in practice 117 // we rarely have more than a few tens of instances and so 118 // it's unlikely that we'll exhaust the 52 bits in a float64. 119 key = addrs.IntKey(int(tk)) 120 case string: 121 key = addrs.StringKey(tk) 122 default: 123 if keyRaw != nil { 124 diags = diags.Append(tfdiags.Sourceless( 125 tfdiags.Error, 126 "Invalid resource instance metadata in state", 127 fmt.Sprintf("Resource %s has an instance with the invalid instance key %#v.", rAddr.Absolute(moduleAddr), keyRaw), 128 )) 129 continue 130 } 131 key = addrs.NoKey 132 } 133 134 instAddr := rAddr.Instance(key) 135 136 obj := &states.ResourceInstanceObjectSrc{ 137 SchemaVersion: isV4.SchemaVersion, 138 CreateBeforeDestroy: isV4.CreateBeforeDestroy, 139 } 140 141 { 142 // Instance attributes 143 switch { 144 case isV4.AttributesRaw != nil: 145 obj.AttrsJSON = isV4.AttributesRaw 146 case isV4.AttributesFlat != nil: 147 obj.AttrsFlat = isV4.AttributesFlat 148 default: 149 // This is odd, but we'll accept it and just treat the 150 // object has being empty. In practice this should arise 151 // only from the contrived sort of state objects we tend 152 // to hand-write inline in tests. 153 obj.AttrsJSON = []byte{'{', '}'} 154 } 155 } 156 157 // Sensitive paths 158 if isV4.AttributeSensitivePaths != nil { 159 paths, pathsDiags := unmarshalPaths([]byte(isV4.AttributeSensitivePaths)) 160 diags = diags.Append(pathsDiags) 161 if pathsDiags.HasErrors() { 162 continue 163 } 164 165 var pvm []cty.PathValueMarks 166 for _, path := range paths { 167 pvm = append(pvm, cty.PathValueMarks{ 168 Path: path, 169 Marks: cty.NewValueMarks(marks.Sensitive), 170 }) 171 } 172 obj.AttrSensitivePaths = pvm 173 } 174 175 { 176 // Status 177 raw := isV4.Status 178 switch raw { 179 case "": 180 obj.Status = states.ObjectReady 181 case "tainted": 182 obj.Status = states.ObjectTainted 183 default: 184 diags = diags.Append(tfdiags.Sourceless( 185 tfdiags.Error, 186 "Invalid resource instance metadata in state", 187 fmt.Sprintf("Instance %s has invalid status %q.", instAddr.Absolute(moduleAddr), raw), 188 )) 189 continue 190 } 191 } 192 193 if raw := isV4.PrivateRaw; len(raw) > 0 { 194 obj.Private = raw 195 } 196 197 { 198 depsRaw := isV4.Dependencies 199 deps := make([]addrs.ConfigResource, 0, len(depsRaw)) 200 for _, depRaw := range depsRaw { 201 addr, addrDiags := addrs.ParseAbsResourceStr(depRaw) 202 diags = diags.Append(addrDiags) 203 if addrDiags.HasErrors() { 204 continue 205 } 206 deps = append(deps, addr.Config()) 207 } 208 obj.Dependencies = deps 209 } 210 211 switch { 212 case isV4.Deposed != "": 213 dk := states.DeposedKey(isV4.Deposed) 214 if len(dk) != 8 { 215 diags = diags.Append(tfdiags.Sourceless( 216 tfdiags.Error, 217 "Invalid resource instance metadata in state", 218 fmt.Sprintf("Instance %s has an object with deposed key %q, which is not correctly formatted.", instAddr.Absolute(moduleAddr), isV4.Deposed), 219 )) 220 continue 221 } 222 is := ms.ResourceInstance(instAddr) 223 if is.HasDeposed(dk) { 224 diags = diags.Append(tfdiags.Sourceless( 225 tfdiags.Error, 226 "Duplicate resource instance in state", 227 fmt.Sprintf("Instance %s deposed object %q appears multiple times in the state file.", instAddr.Absolute(moduleAddr), dk), 228 )) 229 continue 230 } 231 232 ms.SetResourceInstanceDeposed(instAddr, dk, obj, providerAddr) 233 default: 234 is := ms.ResourceInstance(instAddr) 235 if is.HasCurrent() { 236 diags = diags.Append(tfdiags.Sourceless( 237 tfdiags.Error, 238 "Duplicate resource instance in state", 239 fmt.Sprintf("Instance %s appears multiple times in the state file.", instAddr.Absolute(moduleAddr)), 240 )) 241 continue 242 } 243 244 ms.SetResourceInstanceCurrent(instAddr, obj, providerAddr) 245 } 246 } 247 248 // We repeat this after creating the instances because 249 // SetResourceInstanceCurrent automatically resets this metadata based 250 // on the incoming objects. That behavior is useful when we're making 251 // piecemeal updates to the state during an apply, but when we're 252 // reading the state file we want to reflect its contents exactly. 253 ms.SetResourceProvider(rAddr, providerAddr) 254 } 255 256 // The root module is special in that we persist its attributes and thus 257 // need to reload them now. (For descendent modules we just re-calculate 258 // them based on the latest configuration on each run.) 259 { 260 rootModule := state.RootModule() 261 for name, fos := range sV4.RootOutputs { 262 os := &states.OutputValue{ 263 Addr: addrs.AbsOutputValue{ 264 OutputValue: addrs.OutputValue{ 265 Name: name, 266 }, 267 }, 268 } 269 os.Sensitive = fos.Sensitive 270 271 ty, err := ctyjson.UnmarshalType([]byte(fos.ValueTypeRaw)) 272 if err != nil { 273 diags = diags.Append(tfdiags.Sourceless( 274 tfdiags.Error, 275 "Invalid output value type in state", 276 fmt.Sprintf("The state file has an invalid type specification for output %q: %s.", name, err), 277 )) 278 continue 279 } 280 281 val, err := ctyjson.Unmarshal([]byte(fos.ValueRaw), ty) 282 if err != nil { 283 diags = diags.Append(tfdiags.Sourceless( 284 tfdiags.Error, 285 "Invalid output value saved in state", 286 fmt.Sprintf("The state file has an invalid value for output %q: %s.", name, err), 287 )) 288 continue 289 } 290 291 os.Value = val 292 rootModule.OutputValues[name] = os 293 } 294 } 295 296 // Saved check results from the previous run, if any. 297 // We differentiate absense from an empty array here so that we can 298 // recognize if the previous run was with a version of Terraform that 299 // didn't support checks yet, or if there just weren't any checkable 300 // objects to record, in case that's important for certain messaging. 301 if sV4.CheckResults != nil { 302 var moreDiags tfdiags.Diagnostics 303 state.CheckResults, moreDiags = decodeCheckResultsV4(sV4.CheckResults) 304 diags = diags.Append(moreDiags) 305 } 306 307 file.State = state 308 return file, diags 309 } 310 311 func writeStateV4(file *File, w io.Writer) tfdiags.Diagnostics { 312 // Here we'll convert back from the "File" representation to our 313 // stateV4 struct representation and write that. 314 // 315 // While we support legacy state formats for reading, we only support the 316 // latest for writing and so if a V5 is added in future then this function 317 // should be deleted and replaced with a writeStateV5, even though the 318 // read/prepare V4 functions above would stick around. 319 320 var diags tfdiags.Diagnostics 321 if file == nil || file.State == nil { 322 panic("attempt to write nil state to file") 323 } 324 325 var terraformVersion string 326 if file.TerraformVersion != nil { 327 terraformVersion = file.TerraformVersion.String() 328 } 329 330 sV4 := &stateV4{ 331 TerraformVersion: terraformVersion, 332 Serial: file.Serial, 333 Lineage: file.Lineage, 334 RootOutputs: map[string]outputStateV4{}, 335 Resources: []resourceStateV4{}, 336 } 337 338 for name, os := range file.State.RootModule().OutputValues { 339 src, err := ctyjson.Marshal(os.Value, os.Value.Type()) 340 if err != nil { 341 diags = diags.Append(tfdiags.Sourceless( 342 tfdiags.Error, 343 "Failed to serialize output value in state", 344 fmt.Sprintf("An error occured while serializing output value %q: %s.", name, err), 345 )) 346 continue 347 } 348 349 typeSrc, err := ctyjson.MarshalType(os.Value.Type()) 350 if err != nil { 351 diags = diags.Append(tfdiags.Sourceless( 352 tfdiags.Error, 353 "Failed to serialize output value in state", 354 fmt.Sprintf("An error occured while serializing the type of output value %q: %s.", name, err), 355 )) 356 continue 357 } 358 359 sV4.RootOutputs[name] = outputStateV4{ 360 Sensitive: os.Sensitive, 361 ValueRaw: json.RawMessage(src), 362 ValueTypeRaw: json.RawMessage(typeSrc), 363 } 364 } 365 366 for _, ms := range file.State.Modules { 367 moduleAddr := ms.Addr 368 for _, rs := range ms.Resources { 369 resourceAddr := rs.Addr.Resource 370 371 var mode string 372 switch resourceAddr.Mode { 373 case addrs.ManagedResourceMode: 374 mode = "managed" 375 case addrs.DataResourceMode: 376 mode = "data" 377 default: 378 diags = diags.Append(tfdiags.Sourceless( 379 tfdiags.Error, 380 "Failed to serialize resource in state", 381 fmt.Sprintf("Resource %s has mode %s, which cannot be serialized in state", resourceAddr.Absolute(moduleAddr), resourceAddr.Mode), 382 )) 383 continue 384 } 385 386 sV4.Resources = append(sV4.Resources, resourceStateV4{ 387 Module: moduleAddr.String(), 388 Mode: mode, 389 Type: resourceAddr.Type, 390 Name: resourceAddr.Name, 391 ProviderConfig: rs.ProviderConfig.String(), 392 Instances: []instanceObjectStateV4{}, 393 }) 394 rsV4 := &(sV4.Resources[len(sV4.Resources)-1]) 395 396 for key, is := range rs.Instances { 397 if is.HasCurrent() { 398 var objDiags tfdiags.Diagnostics 399 rsV4.Instances, objDiags = appendInstanceObjectStateV4( 400 rs, is, key, is.Current, states.NotDeposed, 401 rsV4.Instances, 402 ) 403 diags = diags.Append(objDiags) 404 } 405 for dk, obj := range is.Deposed { 406 var objDiags tfdiags.Diagnostics 407 rsV4.Instances, objDiags = appendInstanceObjectStateV4( 408 rs, is, key, obj, dk, 409 rsV4.Instances, 410 ) 411 diags = diags.Append(objDiags) 412 } 413 } 414 } 415 } 416 417 if file.State.CheckResults != nil { 418 sV4.CheckResults = encodeCheckResultsV4(file.State.CheckResults) 419 } 420 421 sV4.normalize() 422 423 src, err := json.MarshalIndent(sV4, "", " ") 424 if err != nil { 425 // Shouldn't happen if we do our conversion to *stateV4 correctly above. 426 diags = diags.Append(tfdiags.Sourceless( 427 tfdiags.Error, 428 "Failed to serialize state", 429 fmt.Sprintf("An error occured while serializing the state to save it. This is a bug in Terraform and should be reported: %s.", err), 430 )) 431 return diags 432 } 433 src = append(src, '\n') 434 435 _, err = w.Write(src) 436 if err != nil { 437 diags = diags.Append(tfdiags.Sourceless( 438 tfdiags.Error, 439 "Failed to write state", 440 fmt.Sprintf("An error occured while writing the serialized state: %s.", err), 441 )) 442 return diags 443 } 444 445 return diags 446 } 447 448 func appendInstanceObjectStateV4(rs *states.Resource, is *states.ResourceInstance, key addrs.InstanceKey, obj *states.ResourceInstanceObjectSrc, deposed states.DeposedKey, isV4s []instanceObjectStateV4) ([]instanceObjectStateV4, tfdiags.Diagnostics) { 449 var diags tfdiags.Diagnostics 450 451 var status string 452 switch obj.Status { 453 case states.ObjectReady: 454 status = "" 455 case states.ObjectTainted: 456 status = "tainted" 457 default: 458 diags = diags.Append(tfdiags.Sourceless( 459 tfdiags.Error, 460 "Failed to serialize resource instance in state", 461 fmt.Sprintf("Instance %s has status %s, which cannot be saved in state.", rs.Addr.Instance(key), obj.Status), 462 )) 463 } 464 465 var privateRaw []byte 466 if len(obj.Private) > 0 { 467 privateRaw = obj.Private 468 } 469 470 deps := make([]string, len(obj.Dependencies)) 471 for i, depAddr := range obj.Dependencies { 472 deps[i] = depAddr.String() 473 } 474 475 var rawKey interface{} 476 switch tk := key.(type) { 477 case addrs.IntKey: 478 rawKey = int(tk) 479 case addrs.StringKey: 480 rawKey = string(tk) 481 default: 482 if key != addrs.NoKey { 483 diags = diags.Append(tfdiags.Sourceless( 484 tfdiags.Error, 485 "Failed to serialize resource instance in state", 486 fmt.Sprintf("Instance %s has an unsupported instance key: %#v.", rs.Addr.Instance(key), key), 487 )) 488 } 489 } 490 491 // Extract paths from path value marks 492 var paths []cty.Path 493 for _, vm := range obj.AttrSensitivePaths { 494 paths = append(paths, vm.Path) 495 } 496 497 // Marshal paths to JSON 498 attributeSensitivePaths, pathsDiags := marshalPaths(paths) 499 diags = diags.Append(pathsDiags) 500 501 return append(isV4s, instanceObjectStateV4{ 502 IndexKey: rawKey, 503 Deposed: string(deposed), 504 Status: status, 505 SchemaVersion: obj.SchemaVersion, 506 AttributesFlat: obj.AttrsFlat, 507 AttributesRaw: obj.AttrsJSON, 508 AttributeSensitivePaths: attributeSensitivePaths, 509 PrivateRaw: privateRaw, 510 Dependencies: deps, 511 CreateBeforeDestroy: obj.CreateBeforeDestroy, 512 }), diags 513 } 514 515 func decodeCheckResultsV4(in []checkResultsV4) (*states.CheckResults, tfdiags.Diagnostics) { 516 var diags tfdiags.Diagnostics 517 518 ret := &states.CheckResults{} 519 if len(in) == 0 { 520 return ret, diags 521 } 522 523 ret.ConfigResults = addrs.MakeMap[addrs.ConfigCheckable, *states.CheckResultAggregate]() 524 for _, aggrIn := range in { 525 objectKind := decodeCheckableObjectKindV4(aggrIn.ObjectKind) 526 if objectKind == addrs.CheckableKindInvalid { 527 diags = diags.Append(fmt.Errorf("unsupported checkable object kind %q", aggrIn.ObjectKind)) 528 continue 529 } 530 531 // Some trickiness here: we only have an address parser for 532 // addrs.Checkable and not for addrs.ConfigCheckable, but that's okay 533 // because once we have an addrs.Checkable we can always derive an 534 // addrs.ConfigCheckable from it, and a ConfigCheckable should always 535 // be the same syntax as a Checkable with no index information and 536 // thus we can reuse the same parser for both here. 537 configAddrProxy, moreDiags := addrs.ParseCheckableStr(objectKind, aggrIn.ConfigAddr) 538 diags = diags.Append(moreDiags) 539 if moreDiags.HasErrors() { 540 continue 541 } 542 configAddr := configAddrProxy.ConfigCheckable() 543 if configAddr.String() != configAddrProxy.String() { 544 // This is how we catch if the config address included index 545 // information that would be allowed in a Checkable but not 546 // in a ConfigCheckable. 547 diags = diags.Append(fmt.Errorf("invalid checkable config address %s", aggrIn.ConfigAddr)) 548 continue 549 } 550 551 aggr := &states.CheckResultAggregate{ 552 Status: decodeCheckStatusV4(aggrIn.Status), 553 } 554 555 if len(aggrIn.Objects) != 0 { 556 aggr.ObjectResults = addrs.MakeMap[addrs.Checkable, *states.CheckResultObject]() 557 for _, objectIn := range aggrIn.Objects { 558 objectAddr, moreDiags := addrs.ParseCheckableStr(objectKind, objectIn.ObjectAddr) 559 diags = diags.Append(moreDiags) 560 if moreDiags.HasErrors() { 561 continue 562 } 563 564 obj := &states.CheckResultObject{ 565 Status: decodeCheckStatusV4(objectIn.Status), 566 FailureMessages: objectIn.FailureMessages, 567 } 568 aggr.ObjectResults.Put(objectAddr, obj) 569 } 570 } 571 572 ret.ConfigResults.Put(configAddr, aggr) 573 } 574 575 return ret, diags 576 } 577 578 func encodeCheckResultsV4(in *states.CheckResults) []checkResultsV4 { 579 ret := make([]checkResultsV4, 0, in.ConfigResults.Len()) 580 581 for _, configElem := range in.ConfigResults.Elems { 582 configResultsOut := checkResultsV4{ 583 ObjectKind: encodeCheckableObjectKindV4(configElem.Key.CheckableKind()), 584 ConfigAddr: configElem.Key.String(), 585 Status: encodeCheckStatusV4(configElem.Value.Status), 586 } 587 for _, objectElem := range configElem.Value.ObjectResults.Elems { 588 configResultsOut.Objects = append(configResultsOut.Objects, checkResultsObjectV4{ 589 ObjectAddr: objectElem.Key.String(), 590 Status: encodeCheckStatusV4(objectElem.Value.Status), 591 FailureMessages: objectElem.Value.FailureMessages, 592 }) 593 } 594 595 ret = append(ret, configResultsOut) 596 } 597 598 return ret 599 } 600 601 func decodeCheckStatusV4(in string) checks.Status { 602 switch in { 603 case "pass": 604 return checks.StatusPass 605 case "fail": 606 return checks.StatusFail 607 case "error": 608 return checks.StatusError 609 default: 610 // We'll treat anything else as unknown just as a concession to 611 // forward-compatible parsing, in case a later version of Terraform 612 // introduces a new status. 613 return checks.StatusUnknown 614 } 615 } 616 617 func encodeCheckStatusV4(in checks.Status) string { 618 switch in { 619 case checks.StatusPass: 620 return "pass" 621 case checks.StatusFail: 622 return "fail" 623 case checks.StatusError: 624 return "error" 625 case checks.StatusUnknown: 626 return "unknown" 627 default: 628 panic(fmt.Sprintf("unsupported check status %s", in)) 629 } 630 } 631 632 func decodeCheckableObjectKindV4(in string) addrs.CheckableKind { 633 switch in { 634 case "resource": 635 return addrs.CheckableResource 636 case "output": 637 return addrs.CheckableOutputValue 638 default: 639 // We'll treat anything else as invalid just as a concession to 640 // forward-compatible parsing, in case a later version of Terraform 641 // introduces a new status. 642 return addrs.CheckableKindInvalid 643 } 644 } 645 646 func encodeCheckableObjectKindV4(in addrs.CheckableKind) string { 647 switch in { 648 case addrs.CheckableResource: 649 return "resource" 650 case addrs.CheckableOutputValue: 651 return "output" 652 default: 653 panic(fmt.Sprintf("unsupported checkable object kind %s", in)) 654 } 655 } 656 657 type stateV4 struct { 658 Version stateVersionV4 `json:"version"` 659 TerraformVersion string `json:"terraform_version"` 660 Serial uint64 `json:"serial"` 661 Lineage string `json:"lineage"` 662 RootOutputs map[string]outputStateV4 `json:"outputs"` 663 Resources []resourceStateV4 `json:"resources"` 664 CheckResults []checkResultsV4 `json:"check_results"` 665 } 666 667 // normalize makes some in-place changes to normalize the way items are 668 // stored to ensure that two functionally-equivalent states will be stored 669 // identically. 670 func (s *stateV4) normalize() { 671 sort.Stable(sortResourcesV4(s.Resources)) 672 for _, rs := range s.Resources { 673 sort.Stable(sortInstancesV4(rs.Instances)) 674 } 675 } 676 677 type outputStateV4 struct { 678 ValueRaw json.RawMessage `json:"value"` 679 ValueTypeRaw json.RawMessage `json:"type"` 680 Sensitive bool `json:"sensitive,omitempty"` 681 } 682 683 type resourceStateV4 struct { 684 Module string `json:"module,omitempty"` 685 Mode string `json:"mode"` 686 Type string `json:"type"` 687 Name string `json:"name"` 688 EachMode string `json:"each,omitempty"` 689 ProviderConfig string `json:"provider"` 690 Instances []instanceObjectStateV4 `json:"instances"` 691 } 692 693 type instanceObjectStateV4 struct { 694 IndexKey interface{} `json:"index_key,omitempty"` 695 Status string `json:"status,omitempty"` 696 Deposed string `json:"deposed,omitempty"` 697 698 SchemaVersion uint64 `json:"schema_version"` 699 AttributesRaw json.RawMessage `json:"attributes,omitempty"` 700 AttributesFlat map[string]string `json:"attributes_flat,omitempty"` 701 AttributeSensitivePaths json.RawMessage `json:"sensitive_attributes,omitempty"` 702 703 PrivateRaw []byte `json:"private,omitempty"` 704 705 Dependencies []string `json:"dependencies,omitempty"` 706 707 CreateBeforeDestroy bool `json:"create_before_destroy,omitempty"` 708 } 709 710 type checkResultsV4 struct { 711 ObjectKind string `json:"object_kind"` 712 ConfigAddr string `json:"config_addr"` 713 Status string `json:"status"` 714 Objects []checkResultsObjectV4 `json:"objects"` 715 } 716 717 type checkResultsObjectV4 struct { 718 ObjectAddr string `json:"object_addr"` 719 Status string `json:"status"` 720 FailureMessages []string `json:"failure_messages,omitempty"` 721 } 722 723 // stateVersionV4 is a weird special type we use to produce our hard-coded 724 // "version": 4 in the JSON serialization. 725 type stateVersionV4 struct{} 726 727 func (sv stateVersionV4) MarshalJSON() ([]byte, error) { 728 return []byte{'4'}, nil 729 } 730 731 func (sv stateVersionV4) UnmarshalJSON([]byte) error { 732 // Nothing to do: we already know we're version 4 733 return nil 734 } 735 736 type sortResourcesV4 []resourceStateV4 737 738 func (sr sortResourcesV4) Len() int { return len(sr) } 739 func (sr sortResourcesV4) Swap(i, j int) { sr[i], sr[j] = sr[j], sr[i] } 740 func (sr sortResourcesV4) Less(i, j int) bool { 741 switch { 742 case sr[i].Module != sr[j].Module: 743 return sr[i].Module < sr[j].Module 744 case sr[i].Mode != sr[j].Mode: 745 return sr[i].Mode < sr[j].Mode 746 case sr[i].Type != sr[j].Type: 747 return sr[i].Type < sr[j].Type 748 case sr[i].Name != sr[j].Name: 749 return sr[i].Name < sr[j].Name 750 default: 751 return false 752 } 753 } 754 755 type sortInstancesV4 []instanceObjectStateV4 756 757 func (si sortInstancesV4) Len() int { return len(si) } 758 func (si sortInstancesV4) Swap(i, j int) { si[i], si[j] = si[j], si[i] } 759 func (si sortInstancesV4) Less(i, j int) bool { 760 ki := si[i].IndexKey 761 kj := si[j].IndexKey 762 if ki != kj { 763 if (ki == nil) != (kj == nil) { 764 return ki == nil 765 } 766 if kii, isInt := ki.(int); isInt { 767 if kji, isInt := kj.(int); isInt { 768 return kii < kji 769 } 770 return true 771 } 772 if kis, isStr := ki.(string); isStr { 773 if kjs, isStr := kj.(string); isStr { 774 return kis < kjs 775 } 776 return true 777 } 778 } 779 if si[i].Deposed != si[j].Deposed { 780 return si[i].Deposed < si[j].Deposed 781 } 782 return false 783 } 784 785 // pathStep is an intermediate representation of a cty.PathStep to facilitate 786 // consistent JSON serialization. The Value field can either be a cty.Value of 787 // dynamic type (for index steps), or a string (for get attr steps). 788 type pathStep struct { 789 Type string `json:"type"` 790 Value json.RawMessage `json:"value"` 791 } 792 793 const ( 794 indexPathStepType = "index" 795 getAttrPathStepType = "get_attr" 796 ) 797 798 func unmarshalPaths(buf []byte) ([]cty.Path, tfdiags.Diagnostics) { 799 var diags tfdiags.Diagnostics 800 var jsonPaths [][]pathStep 801 802 err := json.Unmarshal(buf, &jsonPaths) 803 if err != nil { 804 diags = diags.Append(tfdiags.Sourceless( 805 tfdiags.Error, 806 "Error unmarshaling path steps", 807 err.Error(), 808 )) 809 } 810 811 paths := make([]cty.Path, 0, len(jsonPaths)) 812 813 unmarshalOuter: 814 for _, jsonPath := range jsonPaths { 815 var path cty.Path 816 for _, jsonStep := range jsonPath { 817 switch jsonStep.Type { 818 case indexPathStepType: 819 key, err := ctyjson.Unmarshal(jsonStep.Value, cty.DynamicPseudoType) 820 if err != nil { 821 diags = diags.Append(tfdiags.Sourceless( 822 tfdiags.Error, 823 "Error unmarshaling path step", 824 fmt.Sprintf("Failed to unmarshal index step key: %s", err), 825 )) 826 continue unmarshalOuter 827 } 828 path = append(path, cty.IndexStep{Key: key}) 829 case getAttrPathStepType: 830 var name string 831 if err := json.Unmarshal(jsonStep.Value, &name); err != nil { 832 diags = diags.Append(tfdiags.Sourceless( 833 tfdiags.Error, 834 "Error unmarshaling path step", 835 fmt.Sprintf("Failed to unmarshal get attr step name: %s", err), 836 )) 837 continue unmarshalOuter 838 } 839 path = append(path, cty.GetAttrStep{Name: name}) 840 default: 841 diags = diags.Append(tfdiags.Sourceless( 842 tfdiags.Error, 843 "Unsupported path step", 844 fmt.Sprintf("Unsupported path step type %q", jsonStep.Type), 845 )) 846 continue unmarshalOuter 847 } 848 } 849 paths = append(paths, path) 850 } 851 852 return paths, diags 853 } 854 855 func marshalPaths(paths []cty.Path) ([]byte, tfdiags.Diagnostics) { 856 var diags tfdiags.Diagnostics 857 858 // cty.Path is a slice of cty.PathSteps, so our representation of a slice 859 // of paths is a nested slice of our intermediate pathStep struct 860 jsonPaths := make([][]pathStep, 0, len(paths)) 861 862 marshalOuter: 863 for _, path := range paths { 864 jsonPath := make([]pathStep, 0, len(path)) 865 for _, step := range path { 866 var jsonStep pathStep 867 switch s := step.(type) { 868 case cty.IndexStep: 869 key, err := ctyjson.Marshal(s.Key, cty.DynamicPseudoType) 870 if err != nil { 871 diags = diags.Append(tfdiags.Sourceless( 872 tfdiags.Error, 873 "Error marshaling path step", 874 fmt.Sprintf("Failed to marshal index step key %#v: %s", s.Key, err), 875 )) 876 continue marshalOuter 877 } 878 jsonStep.Type = indexPathStepType 879 jsonStep.Value = key 880 case cty.GetAttrStep: 881 name, err := json.Marshal(s.Name) 882 if err != nil { 883 diags = diags.Append(tfdiags.Sourceless( 884 tfdiags.Error, 885 "Error marshaling path step", 886 fmt.Sprintf("Failed to marshal get attr step name %s: %s", s.Name, err), 887 )) 888 continue marshalOuter 889 } 890 jsonStep.Type = getAttrPathStepType 891 jsonStep.Value = name 892 default: 893 diags = diags.Append(tfdiags.Sourceless( 894 tfdiags.Error, 895 "Unsupported path step", 896 fmt.Sprintf("Unsupported path step %#v (%t)", step, step), 897 )) 898 continue marshalOuter 899 } 900 jsonPath = append(jsonPath, jsonStep) 901 } 902 jsonPaths = append(jsonPaths, jsonPath) 903 } 904 905 buf, err := json.Marshal(jsonPaths) 906 if err != nil { 907 diags = diags.Append(tfdiags.Sourceless( 908 tfdiags.Error, 909 "Error marshaling path steps", 910 fmt.Sprintf("Failed to marshal path steps: %s", err), 911 )) 912 } 913 914 return buf, diags 915 }