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