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