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