github.com/opentofu/opentofu@v1.7.1/internal/legacy/helper/schema/resource.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package schema 7 8 import ( 9 "errors" 10 "fmt" 11 "log" 12 "strconv" 13 14 "github.com/opentofu/opentofu/internal/legacy/tofu" 15 "github.com/zclconf/go-cty/cty" 16 ) 17 18 var ReservedDataSourceFields = []string{ 19 "connection", 20 "count", 21 "depends_on", 22 "lifecycle", 23 "provider", 24 "provisioner", 25 } 26 27 var ReservedResourceFields = []string{ 28 "connection", 29 "count", 30 "depends_on", 31 "id", 32 "lifecycle", 33 "provider", 34 "provisioner", 35 } 36 37 // Resource represents a thing in OpenTofu that has a set of configurable 38 // attributes and a lifecycle (create, read, update, delete). 39 // 40 // The Resource schema is an abstraction that allows provider writers to 41 // worry only about CRUD operations while off-loading validation, diff 42 // generation, etc. to this higher level library. 43 // 44 // In spite of the name, this struct is not used only for tofu resources, 45 // but also for data sources. In the case of data sources, the Create, 46 // Update and Delete functions must not be provided. 47 type Resource struct { 48 // Schema is the schema for the configuration of this resource. 49 // 50 // The keys of this map are the configuration keys, and the values 51 // describe the schema of the configuration value. 52 // 53 // The schema is used to represent both configurable data as well 54 // as data that might be computed in the process of creating this 55 // resource. 56 Schema map[string]*Schema 57 58 // SchemaVersion is the version number for this resource's Schema 59 // definition. The current SchemaVersion stored in the state for each 60 // resource. Provider authors can increment this version number 61 // when Schema semantics change. If the State's SchemaVersion is less than 62 // the current SchemaVersion, the InstanceState is yielded to the 63 // MigrateState callback, where the provider can make whatever changes it 64 // needs to update the state to be compatible to the latest version of the 65 // Schema. 66 // 67 // When unset, SchemaVersion defaults to 0, so provider authors can start 68 // their Versioning at any integer >= 1 69 SchemaVersion int 70 71 // MigrateState is deprecated and any new changes to a resource's schema 72 // should be handled by StateUpgraders. Existing MigrateState implementations 73 // should remain for compatibility with existing state. MigrateState will 74 // still be called if the stored SchemaVersion is less than the 75 // first version of the StateUpgraders. 76 // 77 // MigrateState is responsible for updating an InstanceState with an old 78 // version to the format expected by the current version of the Schema. 79 // 80 // It is called during Refresh if the State's stored SchemaVersion is less 81 // than the current SchemaVersion of the Resource. 82 // 83 // The function is yielded the state's stored SchemaVersion and a pointer to 84 // the InstanceState that needs updating, as well as the configured 85 // provider's configured meta interface{}, in case the migration process 86 // needs to make any remote API calls. 87 MigrateState StateMigrateFunc 88 89 // StateUpgraders contains the functions responsible for upgrading an 90 // existing state with an old schema version to a newer schema. It is 91 // called specifically by OpenTofu when the stored schema version is less 92 // than the current SchemaVersion of the Resource. 93 // 94 // StateUpgraders map specific schema versions to a StateUpgrader 95 // function. The registered versions are expected to be ordered, 96 // consecutive values. The initial value may be greater than 0 to account 97 // for legacy schemas that weren't recorded and can be handled by 98 // MigrateState. 99 StateUpgraders []StateUpgrader 100 101 // The functions below are the CRUD operations for this resource. 102 // 103 // The only optional operation is Update. If Update is not implemented, 104 // then updates will not be supported for this resource. 105 // 106 // The ResourceData parameter in the functions below are used to 107 // query configuration and changes for the resource as well as to set 108 // the ID, computed data, etc. 109 // 110 // The interface{} parameter is the result of the ConfigureFunc in 111 // the provider for this resource. If the provider does not define 112 // a ConfigureFunc, this will be nil. This parameter should be used 113 // to store API clients, configuration structures, etc. 114 // 115 // If any errors occur during each of the operation, an error should be 116 // returned. If a resource was partially updated, be careful to enable 117 // partial state mode for ResourceData and use it accordingly. 118 // 119 // Exists is a function that is called to check if a resource still 120 // exists. If this returns false, then this will affect the diff 121 // accordingly. If this function isn't set, it will not be called. You 122 // can also signal existence in the Read method by calling d.SetId("") 123 // if the Resource is no longer present and should be removed from state. 124 // The *ResourceData passed to Exists should _not_ be modified. 125 Create CreateFunc 126 Read ReadFunc 127 Update UpdateFunc 128 Delete DeleteFunc 129 Exists ExistsFunc 130 131 // CustomizeDiff is a custom function for working with the diff that 132 // OpenTofu has created for this resource - it can be used to customize the 133 // diff that has been created, diff values not controlled by configuration, 134 // or even veto the diff altogether and abort the plan. It is passed a 135 // *ResourceDiff, a structure similar to ResourceData but lacking most write 136 // functions like Set, while introducing new functions that work with the 137 // diff such as SetNew, SetNewComputed, and ForceNew. 138 // 139 // The phases OpenTofu runs this in, and the state available via functions 140 // like Get and GetChange, are as follows: 141 // 142 // * New resource: One run with no state 143 // * Existing resource: One run with state 144 // * Existing resource, forced new: One run with state (before ForceNew), 145 // then one run without state (as if new resource) 146 // * Tainted resource: No runs (custom diff logic is skipped) 147 // * Destroy: No runs (standard diff logic is skipped on destroy diffs) 148 // 149 // This function needs to be resilient to support all scenarios. 150 // 151 // If this function needs to access external API resources, remember to flag 152 // the RequiresRefresh attribute mentioned below to ensure that 153 // -refresh=false is blocked when running plan or apply, as this means that 154 // this resource requires refresh-like behaviour to work effectively. 155 // 156 // For the most part, only computed fields can be customized by this 157 // function. 158 // 159 // This function is only allowed on regular resources (not data sources). 160 CustomizeDiff CustomizeDiffFunc 161 162 // Importer is the ResourceImporter implementation for this resource. 163 // If this is nil, then this resource does not support importing. If 164 // this is non-nil, then it supports importing and ResourceImporter 165 // must be validated. The validity of ResourceImporter is verified 166 // by InternalValidate on Resource. 167 Importer *ResourceImporter 168 169 // If non-empty, this string is emitted as a warning during Validate. 170 DeprecationMessage string 171 172 // Timeouts allow users to specify specific time durations in which an 173 // operation should time out, to allow them to extend an action to suit their 174 // usage. For example, a user may specify a large Creation timeout for their 175 // AWS RDS Instance due to it's size, or restoring from a snapshot. 176 // Resource implementors must enable Timeout support by adding the allowed 177 // actions (Create, Read, Update, Delete, Default) to the Resource struct, and 178 // accessing them in the matching methods. 179 Timeouts *ResourceTimeout 180 } 181 182 // ShimInstanceStateFromValue converts a cty.Value to a 183 // tofu.InstanceState. 184 func (r *Resource) ShimInstanceStateFromValue(state cty.Value) (*tofu.InstanceState, error) { 185 // Get the raw shimmed value. While this is correct, the set hashes don't 186 // match those from the Schema. 187 s := tofu.NewInstanceStateShimmedFromValue(state, r.SchemaVersion) 188 189 // We now rebuild the state through the ResourceData, so that the set indexes 190 // match what helper/schema expects. 191 data, err := schemaMap(r.Schema).Data(s, nil) 192 if err != nil { 193 return nil, err 194 } 195 196 s = data.State() 197 if s == nil { 198 s = &tofu.InstanceState{} 199 } 200 return s, nil 201 } 202 203 // See Resource documentation. 204 type CreateFunc func(*ResourceData, interface{}) error 205 206 // See Resource documentation. 207 type ReadFunc func(*ResourceData, interface{}) error 208 209 // See Resource documentation. 210 type UpdateFunc func(*ResourceData, interface{}) error 211 212 // See Resource documentation. 213 type DeleteFunc func(*ResourceData, interface{}) error 214 215 // See Resource documentation. 216 type ExistsFunc func(*ResourceData, interface{}) (bool, error) 217 218 // See Resource documentation. 219 type StateMigrateFunc func( 220 int, *tofu.InstanceState, interface{}) (*tofu.InstanceState, error) 221 222 type StateUpgrader struct { 223 // Version is the version schema that this Upgrader will handle, converting 224 // it to Version+1. 225 Version int 226 227 // Type describes the schema that this function can upgrade. Type is 228 // required to decode the schema if the state was stored in a legacy 229 // flatmap format. 230 Type cty.Type 231 232 // Upgrade takes the JSON encoded state and the provider meta value, and 233 // upgrades the state one single schema version. The provided state is 234 // deocded into the default json types using a map[string]interface{}. It 235 // is up to the StateUpgradeFunc to ensure that the returned value can be 236 // encoded using the new schema. 237 Upgrade StateUpgradeFunc 238 } 239 240 // See StateUpgrader 241 type StateUpgradeFunc func(rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) 242 243 // See Resource documentation. 244 type CustomizeDiffFunc func(*ResourceDiff, interface{}) error 245 246 // Apply creates, updates, and/or deletes a resource. 247 func (r *Resource) Apply( 248 s *tofu.InstanceState, 249 d *tofu.InstanceDiff, 250 meta interface{}) (*tofu.InstanceState, error) { 251 data, err := schemaMap(r.Schema).Data(s, d) 252 if err != nil { 253 return s, err 254 } 255 if s != nil && data != nil { 256 data.providerMeta = s.ProviderMeta 257 } 258 259 // Instance Diff shoould have the timeout info, need to copy it over to the 260 // ResourceData meta 261 rt := ResourceTimeout{} 262 if _, ok := d.Meta[TimeoutKey]; ok { 263 if err := rt.DiffDecode(d); err != nil { 264 log.Printf("[ERR] Error decoding ResourceTimeout: %s", err) 265 } 266 } else if s != nil { 267 if _, ok := s.Meta[TimeoutKey]; ok { 268 if err := rt.StateDecode(s); err != nil { 269 log.Printf("[ERR] Error decoding ResourceTimeout: %s", err) 270 } 271 } 272 } else { 273 log.Printf("[DEBUG] No meta timeoutkey found in Apply()") 274 } 275 data.timeouts = &rt 276 277 if s == nil { 278 // The OpenTofu API dictates that this should never happen, but 279 // it doesn't hurt to be safe in this case. 280 s = new(tofu.InstanceState) 281 } 282 283 if d.Destroy || d.RequiresNew() { 284 if s.ID != "" { 285 // Destroy the resource since it is created 286 if err := r.Delete(data, meta); err != nil { 287 return r.recordCurrentSchemaVersion(data.State()), err 288 } 289 290 // Make sure the ID is gone. 291 data.SetId("") 292 } 293 294 // If we're only destroying, and not creating, then return 295 // now since we're done! 296 if !d.RequiresNew() { 297 return nil, nil 298 } 299 300 // Reset the data to be stateless since we just destroyed 301 data, err = schemaMap(r.Schema).Data(nil, d) 302 // data was reset, need to re-apply the parsed timeouts 303 data.timeouts = &rt 304 if err != nil { 305 return nil, err 306 } 307 } 308 309 err = nil 310 if data.Id() == "" { 311 // We're creating, it is a new resource. 312 data.MarkNewResource() 313 err = r.Create(data, meta) 314 } else { 315 if r.Update == nil { 316 return s, fmt.Errorf("doesn't support update") 317 } 318 319 err = r.Update(data, meta) 320 } 321 322 return r.recordCurrentSchemaVersion(data.State()), err 323 } 324 325 // Diff returns a diff of this resource. 326 func (r *Resource) Diff( 327 s *tofu.InstanceState, 328 c *tofu.ResourceConfig, 329 meta interface{}) (*tofu.InstanceDiff, error) { 330 331 t := &ResourceTimeout{} 332 err := t.ConfigDecode(r, c) 333 334 if err != nil { 335 return nil, fmt.Errorf("[ERR] Error decoding timeout: %w", err) 336 } 337 338 instanceDiff, err := schemaMap(r.Schema).Diff(s, c, r.CustomizeDiff, meta, true) 339 if err != nil { 340 return instanceDiff, err 341 } 342 343 if instanceDiff != nil { 344 if err := t.DiffEncode(instanceDiff); err != nil { 345 log.Printf("[ERR] Error encoding timeout to instance diff: %s", err) 346 } 347 } else { 348 log.Printf("[DEBUG] Instance Diff is nil in Diff()") 349 } 350 351 return instanceDiff, err 352 } 353 354 func (r *Resource) simpleDiff( 355 s *tofu.InstanceState, 356 c *tofu.ResourceConfig, 357 meta interface{}) (*tofu.InstanceDiff, error) { 358 359 instanceDiff, err := schemaMap(r.Schema).Diff(s, c, r.CustomizeDiff, meta, false) 360 if err != nil { 361 return instanceDiff, err 362 } 363 364 if instanceDiff == nil { 365 instanceDiff = tofu.NewInstanceDiff() 366 } 367 368 // Make sure the old value is set in each of the instance diffs. 369 // This was done by the RequiresNew logic in the full legacy Diff. 370 for k, attr := range instanceDiff.Attributes { 371 if attr == nil { 372 continue 373 } 374 if s != nil { 375 attr.Old = s.Attributes[k] 376 } 377 } 378 379 return instanceDiff, nil 380 } 381 382 // Validate validates the resource configuration against the schema. 383 func (r *Resource) Validate(c *tofu.ResourceConfig) ([]string, []error) { 384 warns, errs := schemaMap(r.Schema).Validate(c) 385 386 if r.DeprecationMessage != "" { 387 warns = append(warns, r.DeprecationMessage) 388 } 389 390 return warns, errs 391 } 392 393 // ReadDataApply loads the data for a data source, given a diff that 394 // describes the configuration arguments and desired computed attributes. 395 func (r *Resource) ReadDataApply( 396 d *tofu.InstanceDiff, 397 meta interface{}, 398 ) (*tofu.InstanceState, error) { 399 // Data sources are always built completely from scratch 400 // on each read, so the source state is always nil. 401 data, err := schemaMap(r.Schema).Data(nil, d) 402 if err != nil { 403 return nil, err 404 } 405 406 err = r.Read(data, meta) 407 state := data.State() 408 if state != nil && state.ID == "" { 409 // Data sources can set an ID if they want, but they aren't 410 // required to; we'll provide a placeholder if they don't, 411 // to preserve the invariant that all resources have non-empty 412 // ids. 413 state.ID = "-" 414 } 415 416 return r.recordCurrentSchemaVersion(state), err 417 } 418 419 // RefreshWithoutUpgrade reads the instance state, but does not call 420 // MigrateState or the StateUpgraders, since those are now invoked in a 421 // separate API call. 422 // RefreshWithoutUpgrade is part of the new plugin shims. 423 func (r *Resource) RefreshWithoutUpgrade( 424 s *tofu.InstanceState, 425 meta interface{}) (*tofu.InstanceState, error) { 426 // If the ID is already somehow blank, it doesn't exist 427 if s.ID == "" { 428 return nil, nil 429 } 430 431 rt := ResourceTimeout{} 432 if _, ok := s.Meta[TimeoutKey]; ok { 433 if err := rt.StateDecode(s); err != nil { 434 log.Printf("[ERR] Error decoding ResourceTimeout: %s", err) 435 } 436 } 437 438 if r.Exists != nil { 439 // Make a copy of data so that if it is modified it doesn't 440 // affect our Read later. 441 data, err := schemaMap(r.Schema).Data(s, nil) 442 data.timeouts = &rt 443 444 if err != nil { 445 return s, err 446 } 447 448 if s != nil { 449 data.providerMeta = s.ProviderMeta 450 } 451 452 exists, err := r.Exists(data, meta) 453 if err != nil { 454 return s, err 455 } 456 if !exists { 457 return nil, nil 458 } 459 } 460 461 data, err := schemaMap(r.Schema).Data(s, nil) 462 data.timeouts = &rt 463 if err != nil { 464 return s, err 465 } 466 467 if s != nil { 468 data.providerMeta = s.ProviderMeta 469 } 470 471 err = r.Read(data, meta) 472 state := data.State() 473 if state != nil && state.ID == "" { 474 state = nil 475 } 476 477 return r.recordCurrentSchemaVersion(state), err 478 } 479 480 // Refresh refreshes the state of the resource. 481 func (r *Resource) Refresh( 482 s *tofu.InstanceState, 483 meta interface{}) (*tofu.InstanceState, error) { 484 // If the ID is already somehow blank, it doesn't exist 485 if s.ID == "" { 486 return nil, nil 487 } 488 489 rt := ResourceTimeout{} 490 if _, ok := s.Meta[TimeoutKey]; ok { 491 if err := rt.StateDecode(s); err != nil { 492 log.Printf("[ERR] Error decoding ResourceTimeout: %s", err) 493 } 494 } 495 496 if r.Exists != nil { 497 // Make a copy of data so that if it is modified it doesn't 498 // affect our Read later. 499 data, err := schemaMap(r.Schema).Data(s, nil) 500 data.timeouts = &rt 501 502 if err != nil { 503 return s, err 504 } 505 506 exists, err := r.Exists(data, meta) 507 if err != nil { 508 return s, err 509 } 510 if !exists { 511 return nil, nil 512 } 513 } 514 515 // there may be new StateUpgraders that need to be run 516 s, err := r.upgradeState(s, meta) 517 if err != nil { 518 return s, err 519 } 520 521 data, err := schemaMap(r.Schema).Data(s, nil) 522 data.timeouts = &rt 523 if err != nil { 524 return s, err 525 } 526 527 err = r.Read(data, meta) 528 state := data.State() 529 if state != nil && state.ID == "" { 530 state = nil 531 } 532 533 return r.recordCurrentSchemaVersion(state), err 534 } 535 536 func (r *Resource) upgradeState(s *tofu.InstanceState, meta interface{}) (*tofu.InstanceState, error) { 537 var err error 538 539 needsMigration, stateSchemaVersion := r.checkSchemaVersion(s) 540 migrate := needsMigration && r.MigrateState != nil 541 542 if migrate { 543 s, err = r.MigrateState(stateSchemaVersion, s, meta) 544 if err != nil { 545 return s, err 546 } 547 } 548 549 if len(r.StateUpgraders) == 0 { 550 return s, nil 551 } 552 553 // If we ran MigrateState, then the stateSchemaVersion value is no longer 554 // correct. We can expect the first upgrade function to be the correct 555 // schema type version. 556 if migrate { 557 stateSchemaVersion = r.StateUpgraders[0].Version 558 } 559 560 schemaType := r.CoreConfigSchema().ImpliedType() 561 // find the expected type to convert the state 562 for _, upgrader := range r.StateUpgraders { 563 if stateSchemaVersion == upgrader.Version { 564 schemaType = upgrader.Type 565 } 566 } 567 568 // StateUpgraders only operate on the new JSON format state, so the state 569 // need to be converted. 570 stateVal, err := StateValueFromInstanceState(s, schemaType) 571 if err != nil { 572 return nil, err 573 } 574 575 jsonState, err := StateValueToJSONMap(stateVal, schemaType) 576 if err != nil { 577 return nil, err 578 } 579 580 for _, upgrader := range r.StateUpgraders { 581 if stateSchemaVersion != upgrader.Version { 582 continue 583 } 584 585 jsonState, err = upgrader.Upgrade(jsonState, meta) 586 if err != nil { 587 return nil, err 588 } 589 stateSchemaVersion++ 590 } 591 592 // now we need to re-flatmap the new state 593 stateVal, err = JSONMapToStateValue(jsonState, r.CoreConfigSchema()) 594 if err != nil { 595 return nil, err 596 } 597 598 return r.ShimInstanceStateFromValue(stateVal) 599 } 600 601 // InternalValidate should be called to validate the structure 602 // of the resource. 603 // 604 // This should be called in a unit test for any resource to verify 605 // before release that a resource is properly configured for use with 606 // this library. 607 // 608 // Provider.InternalValidate() will automatically call this for all of 609 // the resources it manages, so you don't need to call this manually if it 610 // is part of a Provider. 611 func (r *Resource) InternalValidate(topSchemaMap schemaMap, writable bool) error { 612 if r == nil { 613 return errors.New("resource is nil") 614 } 615 616 if !writable { 617 if r.Create != nil || r.Update != nil || r.Delete != nil { 618 return fmt.Errorf("must not implement Create, Update or Delete") 619 } 620 621 // CustomizeDiff cannot be defined for read-only resources 622 if r.CustomizeDiff != nil { 623 return fmt.Errorf("cannot implement CustomizeDiff") 624 } 625 } 626 627 tsm := topSchemaMap 628 629 if r.isTopLevel() && writable { 630 // All non-Computed attributes must be ForceNew if Update is not defined 631 if r.Update == nil { 632 nonForceNewAttrs := make([]string, 0) 633 for k, v := range r.Schema { 634 if !v.ForceNew && !v.Computed { 635 nonForceNewAttrs = append(nonForceNewAttrs, k) 636 } 637 } 638 if len(nonForceNewAttrs) > 0 { 639 return fmt.Errorf( 640 "No Update defined, must set ForceNew on: %#v", nonForceNewAttrs) 641 } 642 } else { 643 nonUpdateableAttrs := make([]string, 0) 644 for k, v := range r.Schema { 645 if v.ForceNew || v.Computed && !v.Optional { 646 nonUpdateableAttrs = append(nonUpdateableAttrs, k) 647 } 648 } 649 updateableAttrs := len(r.Schema) - len(nonUpdateableAttrs) 650 if updateableAttrs == 0 { 651 return fmt.Errorf( 652 "All fields are ForceNew or Computed w/out Optional, Update is superfluous") 653 } 654 } 655 656 tsm = schemaMap(r.Schema) 657 658 // Destroy, and Read are required 659 if r.Read == nil { 660 return fmt.Errorf("Read must be implemented") 661 } 662 if r.Delete == nil { 663 return fmt.Errorf("Delete must be implemented") 664 } 665 666 // If we have an importer, we need to verify the importer. 667 if r.Importer != nil { 668 if err := r.Importer.InternalValidate(); err != nil { 669 return err 670 } 671 } 672 673 for k, f := range tsm { 674 if isReservedResourceFieldName(k, f) { 675 return fmt.Errorf("%s is a reserved field name", k) 676 } 677 } 678 } 679 680 lastVersion := -1 681 for _, u := range r.StateUpgraders { 682 if lastVersion >= 0 && u.Version-lastVersion > 1 { 683 return fmt.Errorf("missing schema version between %d and %d", lastVersion, u.Version) 684 } 685 686 if u.Version >= r.SchemaVersion { 687 return fmt.Errorf("StateUpgrader version %d is >= current version %d", u.Version, r.SchemaVersion) 688 } 689 690 if !u.Type.IsObjectType() { 691 return fmt.Errorf("StateUpgrader %d type is not cty.Object", u.Version) 692 } 693 694 if u.Upgrade == nil { 695 return fmt.Errorf("StateUpgrader %d missing StateUpgradeFunc", u.Version) 696 } 697 698 lastVersion = u.Version 699 } 700 701 if lastVersion >= 0 && lastVersion != r.SchemaVersion-1 { 702 return fmt.Errorf("missing StateUpgrader between %d and %d", lastVersion, r.SchemaVersion) 703 } 704 705 // Data source 706 if r.isTopLevel() && !writable { 707 tsm = schemaMap(r.Schema) 708 for k, _ := range tsm { 709 if isReservedDataSourceFieldName(k) { 710 return fmt.Errorf("%s is a reserved field name", k) 711 } 712 } 713 } 714 715 return schemaMap(r.Schema).InternalValidate(tsm) 716 } 717 718 func isReservedDataSourceFieldName(name string) bool { 719 for _, reservedName := range ReservedDataSourceFields { 720 if name == reservedName { 721 return true 722 } 723 } 724 return false 725 } 726 727 func isReservedResourceFieldName(name string, s *Schema) bool { 728 // Allow phasing out "id" 729 // See https://github.com/terraform-providers/terraform-provider-aws/pull/1626#issuecomment-328881415 730 if name == "id" && (s.Deprecated != "" || s.Removed != "") { 731 return false 732 } 733 734 for _, reservedName := range ReservedResourceFields { 735 if name == reservedName { 736 return true 737 } 738 } 739 return false 740 } 741 742 // Data returns a ResourceData struct for this Resource. Each return value 743 // is a separate copy and can be safely modified differently. 744 // 745 // The data returned from this function has no actual affect on the Resource 746 // itself (including the state given to this function). 747 // 748 // This function is useful for unit tests and ResourceImporter functions. 749 func (r *Resource) Data(s *tofu.InstanceState) *ResourceData { 750 result, err := schemaMap(r.Schema).Data(s, nil) 751 if err != nil { 752 // At the time of writing, this isn't possible (Data never returns 753 // non-nil errors). We panic to find this in the future if we have to. 754 // I don't see a reason for Data to ever return an error. 755 panic(err) 756 } 757 758 // load the Resource timeouts 759 result.timeouts = r.Timeouts 760 if result.timeouts == nil { 761 result.timeouts = &ResourceTimeout{} 762 } 763 764 // Set the schema version to latest by default 765 result.meta = map[string]interface{}{ 766 "schema_version": strconv.Itoa(r.SchemaVersion), 767 } 768 769 return result 770 } 771 772 // TestResourceData Yields a ResourceData filled with this resource's schema for use in unit testing 773 // 774 // TODO: May be able to be removed with the above ResourceData function. 775 func (r *Resource) TestResourceData() *ResourceData { 776 return &ResourceData{ 777 schema: r.Schema, 778 } 779 } 780 781 // SchemasForFlatmapPath tries its best to find a sequence of schemas that 782 // the given dot-delimited attribute path traverses through in the schema 783 // of the receiving Resource. 784 func (r *Resource) SchemasForFlatmapPath(path string) []*Schema { 785 return SchemasForFlatmapPath(path, r.Schema) 786 } 787 788 // Returns true if the resource is "top level" i.e. not a sub-resource. 789 func (r *Resource) isTopLevel() bool { 790 // TODO: This is a heuristic; replace with a definitive attribute? 791 return (r.Create != nil || r.Read != nil) 792 } 793 794 // Determines if a given InstanceState needs to be migrated by checking the 795 // stored version number with the current SchemaVersion 796 func (r *Resource) checkSchemaVersion(is *tofu.InstanceState) (bool, int) { 797 // Get the raw interface{} value for the schema version. If it doesn't 798 // exist or is nil then set it to zero. 799 raw := is.Meta["schema_version"] 800 if raw == nil { 801 raw = "0" 802 } 803 804 // Try to convert it to a string. If it isn't a string then we pretend 805 // that it isn't set at all. It should never not be a string unless it 806 // was manually tampered with. 807 rawString, ok := raw.(string) 808 if !ok { 809 rawString = "0" 810 } 811 812 stateSchemaVersion, _ := strconv.Atoi(rawString) 813 814 // Don't run MigrateState if the version is handled by a StateUpgrader, 815 // since StateMigrateFuncs are not required to handle unknown versions 816 maxVersion := r.SchemaVersion 817 if len(r.StateUpgraders) > 0 { 818 maxVersion = r.StateUpgraders[0].Version 819 } 820 821 return stateSchemaVersion < maxVersion, stateSchemaVersion 822 } 823 824 func (r *Resource) recordCurrentSchemaVersion( 825 state *tofu.InstanceState) *tofu.InstanceState { 826 if state != nil && r.SchemaVersion > 0 { 827 if state.Meta == nil { 828 state.Meta = make(map[string]interface{}) 829 } 830 state.Meta["schema_version"] = strconv.Itoa(r.SchemaVersion) 831 } 832 return state 833 } 834 835 // Noop is a convenience implementation of resource function which takes 836 // no action and returns no error. 837 func Noop(*ResourceData, interface{}) error { 838 return nil 839 } 840 841 // RemoveFromState is a convenience implementation of a resource function 842 // which sets the resource ID to empty string (to remove it from state) 843 // and returns no error. 844 func RemoveFromState(d *ResourceData, _ interface{}) error { 845 d.SetId("") 846 return nil 847 }