github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/legacy/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/internal/legacy/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 177 // ShimInstanceStateFromValue converts a cty.Value to a 178 // terraform.InstanceState. 179 func (r *Resource) ShimInstanceStateFromValue(state cty.Value) (*terraform.InstanceState, error) { 180 // Get the raw shimmed value. While this is correct, the set hashes don't 181 // match those from the Schema. 182 s := terraform.NewInstanceStateShimmedFromValue(state, r.SchemaVersion) 183 184 // We now rebuild the state through the ResourceData, so that the set indexes 185 // match what helper/schema expects. 186 data, err := schemaMap(r.Schema).Data(s, nil) 187 if err != nil { 188 return nil, err 189 } 190 191 s = data.State() 192 if s == nil { 193 s = &terraform.InstanceState{} 194 } 195 return s, nil 196 } 197 198 // See Resource documentation. 199 type CreateFunc func(*ResourceData, interface{}) error 200 201 // See Resource documentation. 202 type ReadFunc func(*ResourceData, interface{}) error 203 204 // See Resource documentation. 205 type UpdateFunc func(*ResourceData, interface{}) error 206 207 // See Resource documentation. 208 type DeleteFunc func(*ResourceData, interface{}) error 209 210 // See Resource documentation. 211 type ExistsFunc func(*ResourceData, interface{}) (bool, error) 212 213 // See Resource documentation. 214 type StateMigrateFunc func( 215 int, *terraform.InstanceState, interface{}) (*terraform.InstanceState, error) 216 217 type StateUpgrader struct { 218 // Version is the version schema that this Upgrader will handle, converting 219 // it to Version+1. 220 Version int 221 222 // Type describes the schema that this function can upgrade. Type is 223 // required to decode the schema if the state was stored in a legacy 224 // flatmap format. 225 Type cty.Type 226 227 // Upgrade takes the JSON encoded state and the provider meta value, and 228 // upgrades the state one single schema version. The provided state is 229 // deocded into the default json types using a map[string]interface{}. It 230 // is up to the StateUpgradeFunc to ensure that the returned value can be 231 // encoded using the new schema. 232 Upgrade StateUpgradeFunc 233 } 234 235 // See StateUpgrader 236 type StateUpgradeFunc func(rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) 237 238 // See Resource documentation. 239 type CustomizeDiffFunc func(*ResourceDiff, interface{}) error 240 241 // Apply creates, updates, and/or deletes a resource. 242 func (r *Resource) Apply( 243 s *terraform.InstanceState, 244 d *terraform.InstanceDiff, 245 meta interface{}) (*terraform.InstanceState, error) { 246 data, err := schemaMap(r.Schema).Data(s, d) 247 if err != nil { 248 return s, err 249 } 250 if s != nil && data != nil { 251 data.providerMeta = s.ProviderMeta 252 } 253 254 // Instance Diff shoould have the timeout info, need to copy it over to the 255 // ResourceData meta 256 rt := ResourceTimeout{} 257 if _, ok := d.Meta[TimeoutKey]; ok { 258 if err := rt.DiffDecode(d); err != nil { 259 log.Printf("[ERR] Error decoding ResourceTimeout: %s", err) 260 } 261 } else if s != nil { 262 if _, ok := s.Meta[TimeoutKey]; ok { 263 if err := rt.StateDecode(s); err != nil { 264 log.Printf("[ERR] Error decoding ResourceTimeout: %s", err) 265 } 266 } 267 } else { 268 log.Printf("[DEBUG] No meta timeoutkey found in Apply()") 269 } 270 data.timeouts = &rt 271 272 if s == nil { 273 // The Terraform API dictates that this should never happen, but 274 // it doesn't hurt to be safe in this case. 275 s = new(terraform.InstanceState) 276 } 277 278 if d.Destroy || d.RequiresNew() { 279 if s.ID != "" { 280 // Destroy the resource since it is created 281 if err := r.Delete(data, meta); err != nil { 282 return r.recordCurrentSchemaVersion(data.State()), err 283 } 284 285 // Make sure the ID is gone. 286 data.SetId("") 287 } 288 289 // If we're only destroying, and not creating, then return 290 // now since we're done! 291 if !d.RequiresNew() { 292 return nil, nil 293 } 294 295 // Reset the data to be stateless since we just destroyed 296 data, err = schemaMap(r.Schema).Data(nil, d) 297 // data was reset, need to re-apply the parsed timeouts 298 data.timeouts = &rt 299 if err != nil { 300 return nil, err 301 } 302 } 303 304 err = nil 305 if data.Id() == "" { 306 // We're creating, it is a new resource. 307 data.MarkNewResource() 308 err = r.Create(data, meta) 309 } else { 310 if r.Update == nil { 311 return s, fmt.Errorf("doesn't support update") 312 } 313 314 err = r.Update(data, meta) 315 } 316 317 return r.recordCurrentSchemaVersion(data.State()), err 318 } 319 320 // Diff returns a diff of this resource. 321 func (r *Resource) Diff( 322 s *terraform.InstanceState, 323 c *terraform.ResourceConfig, 324 meta interface{}) (*terraform.InstanceDiff, error) { 325 326 t := &ResourceTimeout{} 327 err := t.ConfigDecode(r, c) 328 329 if err != nil { 330 return nil, fmt.Errorf("[ERR] Error decoding timeout: %s", err) 331 } 332 333 instanceDiff, err := schemaMap(r.Schema).Diff(s, c, r.CustomizeDiff, meta, true) 334 if err != nil { 335 return instanceDiff, err 336 } 337 338 if instanceDiff != nil { 339 if err := t.DiffEncode(instanceDiff); err != nil { 340 log.Printf("[ERR] Error encoding timeout to instance diff: %s", err) 341 } 342 } else { 343 log.Printf("[DEBUG] Instance Diff is nil in Diff()") 344 } 345 346 return instanceDiff, err 347 } 348 349 func (r *Resource) simpleDiff( 350 s *terraform.InstanceState, 351 c *terraform.ResourceConfig, 352 meta interface{}) (*terraform.InstanceDiff, error) { 353 354 instanceDiff, err := schemaMap(r.Schema).Diff(s, c, r.CustomizeDiff, meta, false) 355 if err != nil { 356 return instanceDiff, err 357 } 358 359 if instanceDiff == nil { 360 instanceDiff = terraform.NewInstanceDiff() 361 } 362 363 // Make sure the old value is set in each of the instance diffs. 364 // This was done by the RequiresNew logic in the full legacy Diff. 365 for k, attr := range instanceDiff.Attributes { 366 if attr == nil { 367 continue 368 } 369 if s != nil { 370 attr.Old = s.Attributes[k] 371 } 372 } 373 374 return instanceDiff, nil 375 } 376 377 // Validate validates the resource configuration against the schema. 378 func (r *Resource) Validate(c *terraform.ResourceConfig) ([]string, []error) { 379 warns, errs := schemaMap(r.Schema).Validate(c) 380 381 if r.DeprecationMessage != "" { 382 warns = append(warns, r.DeprecationMessage) 383 } 384 385 return warns, errs 386 } 387 388 // ReadDataApply loads the data for a data source, given a diff that 389 // describes the configuration arguments and desired computed attributes. 390 func (r *Resource) ReadDataApply( 391 d *terraform.InstanceDiff, 392 meta interface{}, 393 ) (*terraform.InstanceState, error) { 394 // Data sources are always built completely from scratch 395 // on each read, so the source state is always nil. 396 data, err := schemaMap(r.Schema).Data(nil, d) 397 if err != nil { 398 return nil, err 399 } 400 401 err = r.Read(data, meta) 402 state := data.State() 403 if state != nil && state.ID == "" { 404 // Data sources can set an ID if they want, but they aren't 405 // required to; we'll provide a placeholder if they don't, 406 // to preserve the invariant that all resources have non-empty 407 // ids. 408 state.ID = "-" 409 } 410 411 return r.recordCurrentSchemaVersion(state), err 412 } 413 414 // RefreshWithoutUpgrade reads the instance state, but does not call 415 // MigrateState or the StateUpgraders, since those are now invoked in a 416 // separate API call. 417 // RefreshWithoutUpgrade is part of the new plugin shims. 418 func (r *Resource) RefreshWithoutUpgrade( 419 s *terraform.InstanceState, 420 meta interface{}) (*terraform.InstanceState, error) { 421 // If the ID is already somehow blank, it doesn't exist 422 if s.ID == "" { 423 return nil, nil 424 } 425 426 rt := ResourceTimeout{} 427 if _, ok := s.Meta[TimeoutKey]; ok { 428 if err := rt.StateDecode(s); err != nil { 429 log.Printf("[ERR] Error decoding ResourceTimeout: %s", err) 430 } 431 } 432 433 if r.Exists != nil { 434 // Make a copy of data so that if it is modified it doesn't 435 // affect our Read later. 436 data, err := schemaMap(r.Schema).Data(s, nil) 437 data.timeouts = &rt 438 439 if err != nil { 440 return s, err 441 } 442 443 if s != nil { 444 data.providerMeta = s.ProviderMeta 445 } 446 447 exists, err := r.Exists(data, meta) 448 if err != nil { 449 return s, err 450 } 451 if !exists { 452 return nil, nil 453 } 454 } 455 456 data, err := schemaMap(r.Schema).Data(s, nil) 457 data.timeouts = &rt 458 if err != nil { 459 return s, err 460 } 461 462 if s != nil { 463 data.providerMeta = s.ProviderMeta 464 } 465 466 err = r.Read(data, meta) 467 state := data.State() 468 if state != nil && state.ID == "" { 469 state = nil 470 } 471 472 return r.recordCurrentSchemaVersion(state), err 473 } 474 475 // Refresh refreshes the state of the resource. 476 func (r *Resource) Refresh( 477 s *terraform.InstanceState, 478 meta interface{}) (*terraform.InstanceState, error) { 479 // If the ID is already somehow blank, it doesn't exist 480 if s.ID == "" { 481 return nil, nil 482 } 483 484 rt := ResourceTimeout{} 485 if _, ok := s.Meta[TimeoutKey]; ok { 486 if err := rt.StateDecode(s); err != nil { 487 log.Printf("[ERR] Error decoding ResourceTimeout: %s", err) 488 } 489 } 490 491 if r.Exists != nil { 492 // Make a copy of data so that if it is modified it doesn't 493 // affect our Read later. 494 data, err := schemaMap(r.Schema).Data(s, nil) 495 data.timeouts = &rt 496 497 if err != nil { 498 return s, err 499 } 500 501 exists, err := r.Exists(data, meta) 502 if err != nil { 503 return s, err 504 } 505 if !exists { 506 return nil, nil 507 } 508 } 509 510 // there may be new StateUpgraders that need to be run 511 s, err := r.upgradeState(s, meta) 512 if err != nil { 513 return s, err 514 } 515 516 data, err := schemaMap(r.Schema).Data(s, nil) 517 data.timeouts = &rt 518 if err != nil { 519 return s, err 520 } 521 522 err = r.Read(data, meta) 523 state := data.State() 524 if state != nil && state.ID == "" { 525 state = nil 526 } 527 528 return r.recordCurrentSchemaVersion(state), err 529 } 530 531 func (r *Resource) upgradeState(s *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) { 532 var err error 533 534 needsMigration, stateSchemaVersion := r.checkSchemaVersion(s) 535 migrate := needsMigration && r.MigrateState != nil 536 537 if migrate { 538 s, err = r.MigrateState(stateSchemaVersion, s, meta) 539 if err != nil { 540 return s, err 541 } 542 } 543 544 if len(r.StateUpgraders) == 0 { 545 return s, nil 546 } 547 548 // If we ran MigrateState, then the stateSchemaVersion value is no longer 549 // correct. We can expect the first upgrade function to be the correct 550 // schema type version. 551 if migrate { 552 stateSchemaVersion = r.StateUpgraders[0].Version 553 } 554 555 schemaType := r.CoreConfigSchema().ImpliedType() 556 // find the expected type to convert the state 557 for _, upgrader := range r.StateUpgraders { 558 if stateSchemaVersion == upgrader.Version { 559 schemaType = upgrader.Type 560 } 561 } 562 563 // StateUpgraders only operate on the new JSON format state, so the state 564 // need to be converted. 565 stateVal, err := StateValueFromInstanceState(s, schemaType) 566 if err != nil { 567 return nil, err 568 } 569 570 jsonState, err := StateValueToJSONMap(stateVal, schemaType) 571 if err != nil { 572 return nil, err 573 } 574 575 for _, upgrader := range r.StateUpgraders { 576 if stateSchemaVersion != upgrader.Version { 577 continue 578 } 579 580 jsonState, err = upgrader.Upgrade(jsonState, meta) 581 if err != nil { 582 return nil, err 583 } 584 stateSchemaVersion++ 585 } 586 587 // now we need to re-flatmap the new state 588 stateVal, err = JSONMapToStateValue(jsonState, r.CoreConfigSchema()) 589 if err != nil { 590 return nil, err 591 } 592 593 return r.ShimInstanceStateFromValue(stateVal) 594 } 595 596 // InternalValidate should be called to validate the structure 597 // of the resource. 598 // 599 // This should be called in a unit test for any resource to verify 600 // before release that a resource is properly configured for use with 601 // this library. 602 // 603 // Provider.InternalValidate() will automatically call this for all of 604 // the resources it manages, so you don't need to call this manually if it 605 // is part of a Provider. 606 func (r *Resource) InternalValidate(topSchemaMap schemaMap, writable bool) error { 607 if r == nil { 608 return errors.New("resource is nil") 609 } 610 611 if !writable { 612 if r.Create != nil || r.Update != nil || r.Delete != nil { 613 return fmt.Errorf("must not implement Create, Update or Delete") 614 } 615 616 // CustomizeDiff cannot be defined for read-only resources 617 if r.CustomizeDiff != nil { 618 return fmt.Errorf("cannot implement CustomizeDiff") 619 } 620 } 621 622 tsm := topSchemaMap 623 624 if r.isTopLevel() && writable { 625 // All non-Computed attributes must be ForceNew if Update is not defined 626 if r.Update == nil { 627 nonForceNewAttrs := make([]string, 0) 628 for k, v := range r.Schema { 629 if !v.ForceNew && !v.Computed { 630 nonForceNewAttrs = append(nonForceNewAttrs, k) 631 } 632 } 633 if len(nonForceNewAttrs) > 0 { 634 return fmt.Errorf( 635 "No Update defined, must set ForceNew on: %#v", nonForceNewAttrs) 636 } 637 } else { 638 nonUpdateableAttrs := make([]string, 0) 639 for k, v := range r.Schema { 640 if v.ForceNew || v.Computed && !v.Optional { 641 nonUpdateableAttrs = append(nonUpdateableAttrs, k) 642 } 643 } 644 updateableAttrs := len(r.Schema) - len(nonUpdateableAttrs) 645 if updateableAttrs == 0 { 646 return fmt.Errorf( 647 "All fields are ForceNew or Computed w/out Optional, Update is superfluous") 648 } 649 } 650 651 tsm = schemaMap(r.Schema) 652 653 // Destroy, and Read are required 654 if r.Read == nil { 655 return fmt.Errorf("Read must be implemented") 656 } 657 if r.Delete == nil { 658 return fmt.Errorf("Delete must be implemented") 659 } 660 661 // If we have an importer, we need to verify the importer. 662 if r.Importer != nil { 663 if err := r.Importer.InternalValidate(); err != nil { 664 return err 665 } 666 } 667 668 for k, f := range tsm { 669 if isReservedResourceFieldName(k, f) { 670 return fmt.Errorf("%s is a reserved field name", k) 671 } 672 } 673 } 674 675 lastVersion := -1 676 for _, u := range r.StateUpgraders { 677 if lastVersion >= 0 && u.Version-lastVersion > 1 { 678 return fmt.Errorf("missing schema version between %d and %d", lastVersion, u.Version) 679 } 680 681 if u.Version >= r.SchemaVersion { 682 return fmt.Errorf("StateUpgrader version %d is >= current version %d", u.Version, r.SchemaVersion) 683 } 684 685 if !u.Type.IsObjectType() { 686 return fmt.Errorf("StateUpgrader %d type is not cty.Object", u.Version) 687 } 688 689 if u.Upgrade == nil { 690 return fmt.Errorf("StateUpgrader %d missing StateUpgradeFunc", u.Version) 691 } 692 693 lastVersion = u.Version 694 } 695 696 if lastVersion >= 0 && lastVersion != r.SchemaVersion-1 { 697 return fmt.Errorf("missing StateUpgrader between %d and %d", lastVersion, r.SchemaVersion) 698 } 699 700 // Data source 701 if r.isTopLevel() && !writable { 702 tsm = schemaMap(r.Schema) 703 for k, _ := range tsm { 704 if isReservedDataSourceFieldName(k) { 705 return fmt.Errorf("%s is a reserved field name", k) 706 } 707 } 708 } 709 710 return schemaMap(r.Schema).InternalValidate(tsm) 711 } 712 713 func isReservedDataSourceFieldName(name string) bool { 714 for _, reservedName := range ReservedDataSourceFields { 715 if name == reservedName { 716 return true 717 } 718 } 719 return false 720 } 721 722 func isReservedResourceFieldName(name string, s *Schema) bool { 723 // Allow phasing out "id" 724 // See https://github.com/terraform-providers/terraform-provider-aws/pull/1626#issuecomment-328881415 725 if name == "id" && (s.Deprecated != "" || s.Removed != "") { 726 return false 727 } 728 729 for _, reservedName := range ReservedResourceFields { 730 if name == reservedName { 731 return true 732 } 733 } 734 return false 735 } 736 737 // Data returns a ResourceData struct for this Resource. Each return value 738 // is a separate copy and can be safely modified differently. 739 // 740 // The data returned from this function has no actual affect on the Resource 741 // itself (including the state given to this function). 742 // 743 // This function is useful for unit tests and ResourceImporter functions. 744 func (r *Resource) Data(s *terraform.InstanceState) *ResourceData { 745 result, err := schemaMap(r.Schema).Data(s, nil) 746 if err != nil { 747 // At the time of writing, this isn't possible (Data never returns 748 // non-nil errors). We panic to find this in the future if we have to. 749 // I don't see a reason for Data to ever return an error. 750 panic(err) 751 } 752 753 // load the Resource timeouts 754 result.timeouts = r.Timeouts 755 if result.timeouts == nil { 756 result.timeouts = &ResourceTimeout{} 757 } 758 759 // Set the schema version to latest by default 760 result.meta = map[string]interface{}{ 761 "schema_version": strconv.Itoa(r.SchemaVersion), 762 } 763 764 return result 765 } 766 767 // TestResourceData Yields a ResourceData filled with this resource's schema for use in unit testing 768 // 769 // TODO: May be able to be removed with the above ResourceData function. 770 func (r *Resource) TestResourceData() *ResourceData { 771 return &ResourceData{ 772 schema: r.Schema, 773 } 774 } 775 776 // SchemasForFlatmapPath tries its best to find a sequence of schemas that 777 // the given dot-delimited attribute path traverses through in the schema 778 // of the receiving Resource. 779 func (r *Resource) SchemasForFlatmapPath(path string) []*Schema { 780 return SchemasForFlatmapPath(path, r.Schema) 781 } 782 783 // Returns true if the resource is "top level" i.e. not a sub-resource. 784 func (r *Resource) isTopLevel() bool { 785 // TODO: This is a heuristic; replace with a definitive attribute? 786 return (r.Create != nil || r.Read != nil) 787 } 788 789 // Determines if a given InstanceState needs to be migrated by checking the 790 // stored version number with the current SchemaVersion 791 func (r *Resource) checkSchemaVersion(is *terraform.InstanceState) (bool, int) { 792 // Get the raw interface{} value for the schema version. If it doesn't 793 // exist or is nil then set it to zero. 794 raw := is.Meta["schema_version"] 795 if raw == nil { 796 raw = "0" 797 } 798 799 // Try to convert it to a string. If it isn't a string then we pretend 800 // that it isn't set at all. It should never not be a string unless it 801 // was manually tampered with. 802 rawString, ok := raw.(string) 803 if !ok { 804 rawString = "0" 805 } 806 807 stateSchemaVersion, _ := strconv.Atoi(rawString) 808 809 // Don't run MigrateState if the version is handled by a StateUpgrader, 810 // since StateMigrateFuncs are not required to handle unknown versions 811 maxVersion := r.SchemaVersion 812 if len(r.StateUpgraders) > 0 { 813 maxVersion = r.StateUpgraders[0].Version 814 } 815 816 return stateSchemaVersion < maxVersion, stateSchemaVersion 817 } 818 819 func (r *Resource) recordCurrentSchemaVersion( 820 state *terraform.InstanceState) *terraform.InstanceState { 821 if state != nil && r.SchemaVersion > 0 { 822 if state.Meta == nil { 823 state.Meta = make(map[string]interface{}) 824 } 825 state.Meta["schema_version"] = strconv.Itoa(r.SchemaVersion) 826 } 827 return state 828 } 829 830 // Noop is a convenience implementation of resource function which takes 831 // no action and returns no error. 832 func Noop(*ResourceData, interface{}) error { 833 return nil 834 } 835 836 // RemoveFromState is a convenience implementation of a resource function 837 // which sets the resource ID to empty string (to remove it from state) 838 // and returns no error. 839 func RemoveFromState(d *ResourceData, _ interface{}) error { 840 d.SetId("") 841 return nil 842 }