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