github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/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 sV4.CheckResults = encodeCheckResultsV4(file.State.CheckResults) 418 419 sV4.normalize() 420 421 src, err := json.MarshalIndent(sV4, "", " ") 422 if err != nil { 423 // Shouldn't happen if we do our conversion to *stateV4 correctly above. 424 diags = diags.Append(tfdiags.Sourceless( 425 tfdiags.Error, 426 "Failed to serialize state", 427 fmt.Sprintf("An error occured while serializing the state to save it. This is a bug in Terraform and should be reported: %s.", err), 428 )) 429 return diags 430 } 431 src = append(src, '\n') 432 433 _, err = w.Write(src) 434 if err != nil { 435 diags = diags.Append(tfdiags.Sourceless( 436 tfdiags.Error, 437 "Failed to write state", 438 fmt.Sprintf("An error occured while writing the serialized state: %s.", err), 439 )) 440 return diags 441 } 442 443 return diags 444 } 445 446 func appendInstanceObjectStateV4(rs *states.Resource, is *states.ResourceInstance, key addrs.InstanceKey, obj *states.ResourceInstanceObjectSrc, deposed states.DeposedKey, isV4s []instanceObjectStateV4) ([]instanceObjectStateV4, tfdiags.Diagnostics) { 447 var diags tfdiags.Diagnostics 448 449 var status string 450 switch obj.Status { 451 case states.ObjectReady: 452 status = "" 453 case states.ObjectTainted: 454 status = "tainted" 455 default: 456 diags = diags.Append(tfdiags.Sourceless( 457 tfdiags.Error, 458 "Failed to serialize resource instance in state", 459 fmt.Sprintf("Instance %s has status %s, which cannot be saved in state.", rs.Addr.Instance(key), obj.Status), 460 )) 461 } 462 463 var privateRaw []byte 464 if len(obj.Private) > 0 { 465 privateRaw = obj.Private 466 } 467 468 deps := make([]string, len(obj.Dependencies)) 469 for i, depAddr := range obj.Dependencies { 470 deps[i] = depAddr.String() 471 } 472 473 var rawKey interface{} 474 switch tk := key.(type) { 475 case addrs.IntKey: 476 rawKey = int(tk) 477 case addrs.StringKey: 478 rawKey = string(tk) 479 default: 480 if key != addrs.NoKey { 481 diags = diags.Append(tfdiags.Sourceless( 482 tfdiags.Error, 483 "Failed to serialize resource instance in state", 484 fmt.Sprintf("Instance %s has an unsupported instance key: %#v.", rs.Addr.Instance(key), key), 485 )) 486 } 487 } 488 489 // Extract paths from path value marks 490 var paths []cty.Path 491 for _, vm := range obj.AttrSensitivePaths { 492 paths = append(paths, vm.Path) 493 } 494 495 // Marshal paths to JSON 496 attributeSensitivePaths, pathsDiags := marshalPaths(paths) 497 diags = diags.Append(pathsDiags) 498 499 return append(isV4s, instanceObjectStateV4{ 500 IndexKey: rawKey, 501 Deposed: string(deposed), 502 Status: status, 503 SchemaVersion: obj.SchemaVersion, 504 AttributesFlat: obj.AttrsFlat, 505 AttributesRaw: obj.AttrsJSON, 506 AttributeSensitivePaths: attributeSensitivePaths, 507 PrivateRaw: privateRaw, 508 Dependencies: deps, 509 CreateBeforeDestroy: obj.CreateBeforeDestroy, 510 }), diags 511 } 512 513 func decodeCheckResultsV4(in []checkResultsV4) (*states.CheckResults, tfdiags.Diagnostics) { 514 var diags tfdiags.Diagnostics 515 516 ret := &states.CheckResults{} 517 if len(in) == 0 { 518 return ret, diags 519 } 520 521 ret.ConfigResults = addrs.MakeMap[addrs.ConfigCheckable, *states.CheckResultAggregate]() 522 for _, aggrIn := range in { 523 objectKind := decodeCheckableObjectKindV4(aggrIn.ObjectKind) 524 if objectKind == addrs.CheckableKindInvalid { 525 diags = diags.Append(fmt.Errorf("unsupported checkable object kind %q", aggrIn.ObjectKind)) 526 continue 527 } 528 529 // Some trickiness here: we only have an address parser for 530 // addrs.Checkable and not for addrs.ConfigCheckable, but that's okay 531 // because once we have an addrs.Checkable we can always derive an 532 // addrs.ConfigCheckable from it, and a ConfigCheckable should always 533 // be the same syntax as a Checkable with no index information and 534 // thus we can reuse the same parser for both here. 535 configAddrProxy, moreDiags := addrs.ParseCheckableStr(objectKind, aggrIn.ConfigAddr) 536 diags = diags.Append(moreDiags) 537 if moreDiags.HasErrors() { 538 continue 539 } 540 configAddr := configAddrProxy.ConfigCheckable() 541 if configAddr.String() != configAddrProxy.String() { 542 // This is how we catch if the config address included index 543 // information that would be allowed in a Checkable but not 544 // in a ConfigCheckable. 545 diags = diags.Append(fmt.Errorf("invalid checkable config address %s", aggrIn.ConfigAddr)) 546 continue 547 } 548 549 aggr := &states.CheckResultAggregate{ 550 Status: decodeCheckStatusV4(aggrIn.Status), 551 } 552 553 if len(aggrIn.Objects) != 0 { 554 aggr.ObjectResults = addrs.MakeMap[addrs.Checkable, *states.CheckResultObject]() 555 for _, objectIn := range aggrIn.Objects { 556 objectAddr, moreDiags := addrs.ParseCheckableStr(objectKind, objectIn.ObjectAddr) 557 diags = diags.Append(moreDiags) 558 if moreDiags.HasErrors() { 559 continue 560 } 561 562 obj := &states.CheckResultObject{ 563 Status: decodeCheckStatusV4(objectIn.Status), 564 FailureMessages: objectIn.FailureMessages, 565 } 566 aggr.ObjectResults.Put(objectAddr, obj) 567 } 568 } 569 570 ret.ConfigResults.Put(configAddr, aggr) 571 } 572 573 return ret, diags 574 } 575 576 func encodeCheckResultsV4(in *states.CheckResults) []checkResultsV4 { 577 // normalize empty and nil sets in the serialized state 578 if in == nil || in.ConfigResults.Len() == 0 { 579 return nil 580 } 581 582 ret := make([]checkResultsV4, 0, in.ConfigResults.Len()) 583 584 for _, configElem := range in.ConfigResults.Elems { 585 configResultsOut := checkResultsV4{ 586 ObjectKind: encodeCheckableObjectKindV4(configElem.Key.CheckableKind()), 587 ConfigAddr: configElem.Key.String(), 588 Status: encodeCheckStatusV4(configElem.Value.Status), 589 } 590 for _, objectElem := range configElem.Value.ObjectResults.Elems { 591 configResultsOut.Objects = append(configResultsOut.Objects, checkResultsObjectV4{ 592 ObjectAddr: objectElem.Key.String(), 593 Status: encodeCheckStatusV4(objectElem.Value.Status), 594 FailureMessages: objectElem.Value.FailureMessages, 595 }) 596 } 597 598 ret = append(ret, configResultsOut) 599 } 600 601 return ret 602 } 603 604 func decodeCheckStatusV4(in string) checks.Status { 605 switch in { 606 case "pass": 607 return checks.StatusPass 608 case "fail": 609 return checks.StatusFail 610 case "error": 611 return checks.StatusError 612 default: 613 // We'll treat anything else as unknown just as a concession to 614 // forward-compatible parsing, in case a later version of Terraform 615 // introduces a new status. 616 return checks.StatusUnknown 617 } 618 } 619 620 func encodeCheckStatusV4(in checks.Status) string { 621 switch in { 622 case checks.StatusPass: 623 return "pass" 624 case checks.StatusFail: 625 return "fail" 626 case checks.StatusError: 627 return "error" 628 case checks.StatusUnknown: 629 return "unknown" 630 default: 631 panic(fmt.Sprintf("unsupported check status %s", in)) 632 } 633 } 634 635 func decodeCheckableObjectKindV4(in string) addrs.CheckableKind { 636 switch in { 637 case "resource": 638 return addrs.CheckableResource 639 case "output": 640 return addrs.CheckableOutputValue 641 default: 642 // We'll treat anything else as invalid just as a concession to 643 // forward-compatible parsing, in case a later version of Terraform 644 // introduces a new status. 645 return addrs.CheckableKindInvalid 646 } 647 } 648 649 func encodeCheckableObjectKindV4(in addrs.CheckableKind) string { 650 switch in { 651 case addrs.CheckableResource: 652 return "resource" 653 case addrs.CheckableOutputValue: 654 return "output" 655 default: 656 panic(fmt.Sprintf("unsupported checkable object kind %s", in)) 657 } 658 } 659 660 type stateV4 struct { 661 Version stateVersionV4 `json:"version"` 662 TerraformVersion string `json:"terraform_version"` 663 Serial uint64 `json:"serial"` 664 Lineage string `json:"lineage"` 665 RootOutputs map[string]outputStateV4 `json:"outputs"` 666 Resources []resourceStateV4 `json:"resources"` 667 CheckResults []checkResultsV4 `json:"check_results"` 668 } 669 670 // normalize makes some in-place changes to normalize the way items are 671 // stored to ensure that two functionally-equivalent states will be stored 672 // identically. 673 func (s *stateV4) normalize() { 674 sort.Stable(sortResourcesV4(s.Resources)) 675 for _, rs := range s.Resources { 676 sort.Stable(sortInstancesV4(rs.Instances)) 677 } 678 } 679 680 type outputStateV4 struct { 681 ValueRaw json.RawMessage `json:"value"` 682 ValueTypeRaw json.RawMessage `json:"type"` 683 Sensitive bool `json:"sensitive,omitempty"` 684 } 685 686 type resourceStateV4 struct { 687 Module string `json:"module,omitempty"` 688 Mode string `json:"mode"` 689 Type string `json:"type"` 690 Name string `json:"name"` 691 EachMode string `json:"each,omitempty"` 692 ProviderConfig string `json:"provider"` 693 Instances []instanceObjectStateV4 `json:"instances"` 694 } 695 696 type instanceObjectStateV4 struct { 697 IndexKey interface{} `json:"index_key,omitempty"` 698 Status string `json:"status,omitempty"` 699 Deposed string `json:"deposed,omitempty"` 700 701 SchemaVersion uint64 `json:"schema_version"` 702 AttributesRaw json.RawMessage `json:"attributes,omitempty"` 703 AttributesFlat map[string]string `json:"attributes_flat,omitempty"` 704 AttributeSensitivePaths json.RawMessage `json:"sensitive_attributes,omitempty"` 705 706 PrivateRaw []byte `json:"private,omitempty"` 707 708 Dependencies []string `json:"dependencies,omitempty"` 709 710 CreateBeforeDestroy bool `json:"create_before_destroy,omitempty"` 711 } 712 713 type checkResultsV4 struct { 714 ObjectKind string `json:"object_kind"` 715 ConfigAddr string `json:"config_addr"` 716 Status string `json:"status"` 717 Objects []checkResultsObjectV4 `json:"objects"` 718 } 719 720 type checkResultsObjectV4 struct { 721 ObjectAddr string `json:"object_addr"` 722 Status string `json:"status"` 723 FailureMessages []string `json:"failure_messages,omitempty"` 724 } 725 726 // stateVersionV4 is a weird special type we use to produce our hard-coded 727 // "version": 4 in the JSON serialization. 728 type stateVersionV4 struct{} 729 730 func (sv stateVersionV4) MarshalJSON() ([]byte, error) { 731 return []byte{'4'}, nil 732 } 733 734 func (sv stateVersionV4) UnmarshalJSON([]byte) error { 735 // Nothing to do: we already know we're version 4 736 return nil 737 } 738 739 type sortResourcesV4 []resourceStateV4 740 741 func (sr sortResourcesV4) Len() int { return len(sr) } 742 func (sr sortResourcesV4) Swap(i, j int) { sr[i], sr[j] = sr[j], sr[i] } 743 func (sr sortResourcesV4) Less(i, j int) bool { 744 switch { 745 case sr[i].Module != sr[j].Module: 746 return sr[i].Module < sr[j].Module 747 case sr[i].Mode != sr[j].Mode: 748 return sr[i].Mode < sr[j].Mode 749 case sr[i].Type != sr[j].Type: 750 return sr[i].Type < sr[j].Type 751 case sr[i].Name != sr[j].Name: 752 return sr[i].Name < sr[j].Name 753 default: 754 return false 755 } 756 } 757 758 type sortInstancesV4 []instanceObjectStateV4 759 760 func (si sortInstancesV4) Len() int { return len(si) } 761 func (si sortInstancesV4) Swap(i, j int) { si[i], si[j] = si[j], si[i] } 762 func (si sortInstancesV4) Less(i, j int) bool { 763 ki := si[i].IndexKey 764 kj := si[j].IndexKey 765 if ki != kj { 766 if (ki == nil) != (kj == nil) { 767 return ki == nil 768 } 769 if kii, isInt := ki.(int); isInt { 770 if kji, isInt := kj.(int); isInt { 771 return kii < kji 772 } 773 return true 774 } 775 if kis, isStr := ki.(string); isStr { 776 if kjs, isStr := kj.(string); isStr { 777 return kis < kjs 778 } 779 return true 780 } 781 } 782 if si[i].Deposed != si[j].Deposed { 783 return si[i].Deposed < si[j].Deposed 784 } 785 return false 786 } 787 788 // pathStep is an intermediate representation of a cty.PathStep to facilitate 789 // consistent JSON serialization. The Value field can either be a cty.Value of 790 // dynamic type (for index steps), or a string (for get attr steps). 791 type pathStep struct { 792 Type string `json:"type"` 793 Value json.RawMessage `json:"value"` 794 } 795 796 const ( 797 indexPathStepType = "index" 798 getAttrPathStepType = "get_attr" 799 ) 800 801 func unmarshalPaths(buf []byte) ([]cty.Path, tfdiags.Diagnostics) { 802 var diags tfdiags.Diagnostics 803 var jsonPaths [][]pathStep 804 805 err := json.Unmarshal(buf, &jsonPaths) 806 if err != nil { 807 diags = diags.Append(tfdiags.Sourceless( 808 tfdiags.Error, 809 "Error unmarshaling path steps", 810 err.Error(), 811 )) 812 } 813 814 paths := make([]cty.Path, 0, len(jsonPaths)) 815 816 unmarshalOuter: 817 for _, jsonPath := range jsonPaths { 818 var path cty.Path 819 for _, jsonStep := range jsonPath { 820 switch jsonStep.Type { 821 case indexPathStepType: 822 key, err := ctyjson.Unmarshal(jsonStep.Value, cty.DynamicPseudoType) 823 if err != nil { 824 diags = diags.Append(tfdiags.Sourceless( 825 tfdiags.Error, 826 "Error unmarshaling path step", 827 fmt.Sprintf("Failed to unmarshal index step key: %s", err), 828 )) 829 continue unmarshalOuter 830 } 831 path = append(path, cty.IndexStep{Key: key}) 832 case getAttrPathStepType: 833 var name string 834 if err := json.Unmarshal(jsonStep.Value, &name); err != nil { 835 diags = diags.Append(tfdiags.Sourceless( 836 tfdiags.Error, 837 "Error unmarshaling path step", 838 fmt.Sprintf("Failed to unmarshal get attr step name: %s", err), 839 )) 840 continue unmarshalOuter 841 } 842 path = append(path, cty.GetAttrStep{Name: name}) 843 default: 844 diags = diags.Append(tfdiags.Sourceless( 845 tfdiags.Error, 846 "Unsupported path step", 847 fmt.Sprintf("Unsupported path step type %q", jsonStep.Type), 848 )) 849 continue unmarshalOuter 850 } 851 } 852 paths = append(paths, path) 853 } 854 855 return paths, diags 856 } 857 858 func marshalPaths(paths []cty.Path) ([]byte, tfdiags.Diagnostics) { 859 var diags tfdiags.Diagnostics 860 861 // cty.Path is a slice of cty.PathSteps, so our representation of a slice 862 // of paths is a nested slice of our intermediate pathStep struct 863 jsonPaths := make([][]pathStep, 0, len(paths)) 864 865 marshalOuter: 866 for _, path := range paths { 867 jsonPath := make([]pathStep, 0, len(path)) 868 for _, step := range path { 869 var jsonStep pathStep 870 switch s := step.(type) { 871 case cty.IndexStep: 872 key, err := ctyjson.Marshal(s.Key, cty.DynamicPseudoType) 873 if err != nil { 874 diags = diags.Append(tfdiags.Sourceless( 875 tfdiags.Error, 876 "Error marshaling path step", 877 fmt.Sprintf("Failed to marshal index step key %#v: %s", s.Key, err), 878 )) 879 continue marshalOuter 880 } 881 jsonStep.Type = indexPathStepType 882 jsonStep.Value = key 883 case cty.GetAttrStep: 884 name, err := json.Marshal(s.Name) 885 if err != nil { 886 diags = diags.Append(tfdiags.Sourceless( 887 tfdiags.Error, 888 "Error marshaling path step", 889 fmt.Sprintf("Failed to marshal get attr step name %s: %s", s.Name, err), 890 )) 891 continue marshalOuter 892 } 893 jsonStep.Type = getAttrPathStepType 894 jsonStep.Value = name 895 default: 896 diags = diags.Append(tfdiags.Sourceless( 897 tfdiags.Error, 898 "Unsupported path step", 899 fmt.Sprintf("Unsupported path step %#v (%t)", step, step), 900 )) 901 continue marshalOuter 902 } 903 jsonPath = append(jsonPath, jsonStep) 904 } 905 jsonPaths = append(jsonPaths, jsonPath) 906 } 907 908 buf, err := json.Marshal(jsonPaths) 909 if err != nil { 910 diags = diags.Append(tfdiags.Sourceless( 911 tfdiags.Error, 912 "Error marshaling path steps", 913 fmt.Sprintf("Failed to marshal path steps: %s", err), 914 )) 915 } 916 917 return buf, diags 918 }