github.com/hashicorp/terraform-plugin-sdk@v1.17.2/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 ctyjson "github.com/zclconf/go-cty/cty/json" 11 12 "github.com/hashicorp/terraform-plugin-sdk/internal/addrs" 13 "github.com/hashicorp/terraform-plugin-sdk/internal/states" 14 "github.com/hashicorp/terraform-plugin-sdk/internal/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 depsRaw := isV4.Dependencies 185 deps := make([]addrs.Referenceable, 0, len(depsRaw)) 186 for _, depRaw := range depsRaw { 187 ref, refDiags := addrs.ParseRefStr(depRaw) 188 diags = diags.Append(refDiags) 189 if refDiags.HasErrors() { 190 continue 191 } 192 if len(ref.Remaining) != 0 { 193 diags = diags.Append(tfdiags.Sourceless( 194 tfdiags.Error, 195 "Invalid resource instance metadata in state", 196 fmt.Sprintf("Instance %s declares dependency on %q, which is not a reference to a dependable object.", instAddr.Absolute(moduleAddr), depRaw), 197 )) 198 } 199 if ref.Subject == nil { 200 // Should never happen 201 panic(fmt.Sprintf("parsing dependency %q for instance %s returned a nil address", depRaw, instAddr.Absolute(moduleAddr))) 202 } 203 deps = append(deps, ref.Subject) 204 } 205 obj.Dependencies = deps 206 } 207 208 switch { 209 case isV4.Deposed != "": 210 dk := states.DeposedKey(isV4.Deposed) 211 if len(dk) != 8 { 212 diags = diags.Append(tfdiags.Sourceless( 213 tfdiags.Error, 214 "Invalid resource instance metadata in state", 215 fmt.Sprintf("Instance %s has an object with deposed key %q, which is not correctly formatted.", instAddr.Absolute(moduleAddr), isV4.Deposed), 216 )) 217 continue 218 } 219 is := ms.ResourceInstance(instAddr) 220 if is.HasDeposed(dk) { 221 diags = diags.Append(tfdiags.Sourceless( 222 tfdiags.Error, 223 "Duplicate resource instance in state", 224 fmt.Sprintf("Instance %s deposed object %q appears multiple times in the state file.", instAddr.Absolute(moduleAddr), dk), 225 )) 226 continue 227 } 228 229 ms.SetResourceInstanceDeposed(instAddr, dk, obj, providerAddr) 230 default: 231 is := ms.ResourceInstance(instAddr) 232 if is.HasCurrent() { 233 diags = diags.Append(tfdiags.Sourceless( 234 tfdiags.Error, 235 "Duplicate resource instance in state", 236 fmt.Sprintf("Instance %s appears multiple times in the state file.", instAddr.Absolute(moduleAddr)), 237 )) 238 continue 239 } 240 241 ms.SetResourceInstanceCurrent(instAddr, obj, providerAddr) 242 } 243 } 244 245 // We repeat this after creating the instances because 246 // SetResourceInstanceCurrent automatically resets this metadata based 247 // on the incoming objects. That behavior is useful when we're making 248 // piecemeal updates to the state during an apply, but when we're 249 // reading the state file we want to reflect its contents exactly. 250 ms.SetResourceMeta(rAddr, eachMode, providerAddr) 251 } 252 253 // The root module is special in that we persist its attributes and thus 254 // need to reload them now. (For descendent modules we just re-calculate 255 // them based on the latest configuration on each run.) 256 { 257 rootModule := state.RootModule() 258 for name, fos := range sV4.RootOutputs { 259 os := &states.OutputValue{} 260 os.Sensitive = fos.Sensitive 261 262 ty, err := ctyjson.UnmarshalType([]byte(fos.ValueTypeRaw)) 263 if err != nil { 264 diags = diags.Append(tfdiags.Sourceless( 265 tfdiags.Error, 266 "Invalid output value type in state", 267 fmt.Sprintf("The state file has an invalid type specification for output %q: %s.", name, err), 268 )) 269 continue 270 } 271 272 val, err := ctyjson.Unmarshal([]byte(fos.ValueRaw), ty) 273 if err != nil { 274 diags = diags.Append(tfdiags.Sourceless( 275 tfdiags.Error, 276 "Invalid output value saved in state", 277 fmt.Sprintf("The state file has an invalid value for output %q: %s.", name, err), 278 )) 279 continue 280 } 281 282 os.Value = val 283 rootModule.OutputValues[name] = os 284 } 285 } 286 287 file.State = state 288 return file, diags 289 } 290 291 func writeStateV4(file *File, w io.Writer) tfdiags.Diagnostics { 292 // Here we'll convert back from the "File" representation to our 293 // stateV4 struct representation and write that. 294 // 295 // While we support legacy state formats for reading, we only support the 296 // latest for writing and so if a V5 is added in future then this function 297 // should be deleted and replaced with a writeStateV5, even though the 298 // read/prepare V4 functions above would stick around. 299 300 var diags tfdiags.Diagnostics 301 if file == nil || file.State == nil { 302 panic("attempt to write nil state to file") 303 } 304 305 var terraformVersion string 306 if file.TerraformVersion != nil { 307 terraformVersion = file.TerraformVersion.String() 308 } 309 310 sV4 := &stateV4{ 311 TerraformVersion: terraformVersion, 312 Serial: file.Serial, 313 Lineage: file.Lineage, 314 RootOutputs: map[string]outputStateV4{}, 315 Resources: []resourceStateV4{}, 316 } 317 318 for name, os := range file.State.RootModule().OutputValues { 319 src, err := ctyjson.Marshal(os.Value, os.Value.Type()) 320 if err != nil { 321 diags = diags.Append(tfdiags.Sourceless( 322 tfdiags.Error, 323 "Failed to serialize output value in state", 324 fmt.Sprintf("An error occured while serializing output value %q: %s.", name, err), 325 )) 326 continue 327 } 328 329 typeSrc, err := ctyjson.MarshalType(os.Value.Type()) 330 if err != nil { 331 diags = diags.Append(tfdiags.Sourceless( 332 tfdiags.Error, 333 "Failed to serialize output value in state", 334 fmt.Sprintf("An error occured while serializing the type of output value %q: %s.", name, err), 335 )) 336 continue 337 } 338 339 sV4.RootOutputs[name] = outputStateV4{ 340 Sensitive: os.Sensitive, 341 ValueRaw: json.RawMessage(src), 342 ValueTypeRaw: json.RawMessage(typeSrc), 343 } 344 } 345 346 for _, ms := range file.State.Modules { 347 moduleAddr := ms.Addr 348 for _, rs := range ms.Resources { 349 resourceAddr := rs.Addr 350 351 var mode string 352 switch resourceAddr.Mode { 353 case addrs.ManagedResourceMode: 354 mode = "managed" 355 case addrs.DataResourceMode: 356 mode = "data" 357 default: 358 diags = diags.Append(tfdiags.Sourceless( 359 tfdiags.Error, 360 "Failed to serialize resource in state", 361 fmt.Sprintf("Resource %s has mode %s, which cannot be serialized in state", resourceAddr.Absolute(moduleAddr), resourceAddr.Mode), 362 )) 363 continue 364 } 365 366 var eachMode string 367 switch rs.EachMode { 368 case states.NoEach: 369 eachMode = "" 370 case states.EachList: 371 eachMode = "list" 372 case states.EachMap: 373 eachMode = "map" 374 default: 375 diags = diags.Append(tfdiags.Sourceless( 376 tfdiags.Error, 377 "Failed to serialize resource in state", 378 fmt.Sprintf("Resource %s has \"each\" mode %s, which cannot be serialized in state", resourceAddr.Absolute(moduleAddr), rs.EachMode), 379 )) 380 continue 381 } 382 383 sV4.Resources = append(sV4.Resources, resourceStateV4{ 384 Module: moduleAddr.String(), 385 Mode: mode, 386 Type: resourceAddr.Type, 387 Name: resourceAddr.Name, 388 EachMode: eachMode, 389 ProviderConfig: rs.ProviderConfig.String(), 390 Instances: []instanceObjectStateV4{}, 391 }) 392 rsV4 := &(sV4.Resources[len(sV4.Resources)-1]) 393 394 for key, is := range rs.Instances { 395 if is.HasCurrent() { 396 var objDiags tfdiags.Diagnostics 397 rsV4.Instances, objDiags = appendInstanceObjectStateV4( 398 rs, is, key, is.Current, states.NotDeposed, 399 rsV4.Instances, 400 ) 401 diags = diags.Append(objDiags) 402 } 403 for dk, obj := range is.Deposed { 404 var objDiags tfdiags.Diagnostics 405 rsV4.Instances, objDiags = appendInstanceObjectStateV4( 406 rs, is, key, obj, dk, 407 rsV4.Instances, 408 ) 409 diags = diags.Append(objDiags) 410 } 411 } 412 } 413 } 414 415 sV4.normalize() 416 417 src, err := json.MarshalIndent(sV4, "", " ") 418 if err != nil { 419 // Shouldn't happen if we do our conversion to *stateV4 correctly above. 420 diags = diags.Append(tfdiags.Sourceless( 421 tfdiags.Error, 422 "Failed to serialize state", 423 fmt.Sprintf("An error occured while serializing the state to save it. This is a bug in Terraform and should be reported: %s.", err), 424 )) 425 return diags 426 } 427 src = append(src, '\n') 428 429 _, err = w.Write(src) 430 if err != nil { 431 diags = diags.Append(tfdiags.Sourceless( 432 tfdiags.Error, 433 "Failed to write state", 434 fmt.Sprintf("An error occured while writing the serialized state: %s.", err), 435 )) 436 return diags 437 } 438 439 return diags 440 } 441 442 func appendInstanceObjectStateV4(rs *states.Resource, is *states.ResourceInstance, key addrs.InstanceKey, obj *states.ResourceInstanceObjectSrc, deposed states.DeposedKey, isV4s []instanceObjectStateV4) ([]instanceObjectStateV4, tfdiags.Diagnostics) { 443 var diags tfdiags.Diagnostics 444 445 var status string 446 switch obj.Status { 447 case states.ObjectReady: 448 status = "" 449 case states.ObjectTainted: 450 status = "tainted" 451 default: 452 diags = diags.Append(tfdiags.Sourceless( 453 tfdiags.Error, 454 "Failed to serialize resource instance in state", 455 fmt.Sprintf("Instance %s has status %s, which cannot be saved in state.", rs.Addr.Instance(key), obj.Status), 456 )) 457 } 458 459 var privateRaw []byte 460 if len(obj.Private) > 0 { 461 privateRaw = obj.Private 462 } 463 464 deps := make([]string, len(obj.Dependencies)) 465 for i, depAddr := range obj.Dependencies { 466 deps[i] = depAddr.String() 467 } 468 469 var rawKey interface{} 470 switch tk := key.(type) { 471 case addrs.IntKey: 472 rawKey = int(tk) 473 case addrs.StringKey: 474 rawKey = string(tk) 475 default: 476 if key != addrs.NoKey { 477 diags = diags.Append(tfdiags.Sourceless( 478 tfdiags.Error, 479 "Failed to serialize resource instance in state", 480 fmt.Sprintf("Instance %s has an unsupported instance key: %#v.", rs.Addr.Instance(key), key), 481 )) 482 } 483 } 484 485 return append(isV4s, instanceObjectStateV4{ 486 IndexKey: rawKey, 487 Deposed: string(deposed), 488 Status: status, 489 SchemaVersion: obj.SchemaVersion, 490 AttributesFlat: obj.AttrsFlat, 491 AttributesRaw: obj.AttrsJSON, 492 PrivateRaw: privateRaw, 493 Dependencies: deps, 494 }), diags 495 } 496 497 type stateV4 struct { 498 Version stateVersionV4 `json:"version"` 499 TerraformVersion string `json:"terraform_version"` 500 Serial uint64 `json:"serial"` 501 Lineage string `json:"lineage"` 502 RootOutputs map[string]outputStateV4 `json:"outputs"` 503 Resources []resourceStateV4 `json:"resources"` 504 } 505 506 // normalize makes some in-place changes to normalize the way items are 507 // stored to ensure that two functionally-equivalent states will be stored 508 // identically. 509 func (s *stateV4) normalize() { 510 sort.Stable(sortResourcesV4(s.Resources)) 511 for _, rs := range s.Resources { 512 sort.Stable(sortInstancesV4(rs.Instances)) 513 } 514 } 515 516 type outputStateV4 struct { 517 ValueRaw json.RawMessage `json:"value"` 518 ValueTypeRaw json.RawMessage `json:"type"` 519 Sensitive bool `json:"sensitive,omitempty"` 520 } 521 522 type resourceStateV4 struct { 523 Module string `json:"module,omitempty"` 524 Mode string `json:"mode"` 525 Type string `json:"type"` 526 Name string `json:"name"` 527 EachMode string `json:"each,omitempty"` 528 ProviderConfig string `json:"provider"` 529 Instances []instanceObjectStateV4 `json:"instances"` 530 } 531 532 type instanceObjectStateV4 struct { 533 IndexKey interface{} `json:"index_key,omitempty"` 534 Status string `json:"status,omitempty"` 535 Deposed string `json:"deposed,omitempty"` 536 537 SchemaVersion uint64 `json:"schema_version"` 538 AttributesRaw json.RawMessage `json:"attributes,omitempty"` 539 AttributesFlat map[string]string `json:"attributes_flat,omitempty"` 540 541 PrivateRaw []byte `json:"private,omitempty"` 542 543 Dependencies []string `json:"depends_on,omitempty"` 544 } 545 546 // stateVersionV4 is a weird special type we use to produce our hard-coded 547 // "version": 4 in the JSON serialization. 548 type stateVersionV4 struct{} 549 550 func (sv stateVersionV4) MarshalJSON() ([]byte, error) { 551 return []byte{'4'}, nil 552 } 553 554 func (sv stateVersionV4) UnmarshalJSON([]byte) error { 555 // Nothing to do: we already know we're version 4 556 return nil 557 } 558 559 type sortResourcesV4 []resourceStateV4 560 561 func (sr sortResourcesV4) Len() int { return len(sr) } 562 func (sr sortResourcesV4) Swap(i, j int) { sr[i], sr[j] = sr[j], sr[i] } 563 func (sr sortResourcesV4) Less(i, j int) bool { 564 switch { 565 case sr[i].Mode != sr[j].Mode: 566 return sr[i].Mode < sr[j].Mode 567 case sr[i].Type != sr[j].Type: 568 return sr[i].Type < sr[j].Type 569 case sr[i].Name != sr[j].Name: 570 return sr[i].Name < sr[j].Name 571 default: 572 return false 573 } 574 } 575 576 type sortInstancesV4 []instanceObjectStateV4 577 578 func (si sortInstancesV4) Len() int { return len(si) } 579 func (si sortInstancesV4) Swap(i, j int) { si[i], si[j] = si[j], si[i] } 580 func (si sortInstancesV4) Less(i, j int) bool { 581 ki := si[i].IndexKey 582 kj := si[j].IndexKey 583 if ki != kj { 584 if (ki == nil) != (kj == nil) { 585 return ki == nil 586 } 587 if kii, isInt := ki.(int); isInt { 588 if kji, isInt := kj.(int); isInt { 589 return kii < kji 590 } 591 return true 592 } 593 if kis, isStr := ki.(string); isStr { 594 if kjs, isStr := kj.(string); isStr { 595 return kis < kjs 596 } 597 return true 598 } 599 } 600 if si[i].Deposed != si[j].Deposed { 601 return si[i].Deposed < si[j].Deposed 602 } 603 return false 604 }