diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/helper/schema/resource.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/helper/schema/resource.go | 270 |
1 files changed, 263 insertions, 7 deletions
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/resource.go b/vendor/github.com/hashicorp/terraform/helper/schema/resource.go index d3be2d6..b5e3065 100644 --- a/vendor/github.com/hashicorp/terraform/helper/schema/resource.go +++ b/vendor/github.com/hashicorp/terraform/helper/schema/resource.go | |||
@@ -8,6 +8,7 @@ import ( | |||
8 | 8 | ||
9 | "github.com/hashicorp/terraform/config" | 9 | "github.com/hashicorp/terraform/config" |
10 | "github.com/hashicorp/terraform/terraform" | 10 | "github.com/hashicorp/terraform/terraform" |
11 | "github.com/zclconf/go-cty/cty" | ||
11 | ) | 12 | ) |
12 | 13 | ||
13 | // Resource represents a thing in Terraform that has a set of configurable | 14 | // Resource represents a thing in Terraform that has a set of configurable |
@@ -44,6 +45,12 @@ type Resource struct { | |||
44 | // their Versioning at any integer >= 1 | 45 | // their Versioning at any integer >= 1 |
45 | SchemaVersion int | 46 | SchemaVersion int |
46 | 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 | // | ||
47 | // MigrateState is responsible for updating an InstanceState with an old | 54 | // MigrateState is responsible for updating an InstanceState with an old |
48 | // version to the format expected by the current version of the Schema. | 55 | // version to the format expected by the current version of the Schema. |
49 | // | 56 | // |
@@ -56,6 +63,18 @@ type Resource struct { | |||
56 | // needs to make any remote API calls. | 63 | // needs to make any remote API calls. |
57 | MigrateState StateMigrateFunc | 64 | MigrateState StateMigrateFunc |
58 | 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 | |||
59 | // The functions below are the CRUD operations for this resource. | 78 | // The functions below are the CRUD operations for this resource. |
60 | // | 79 | // |
61 | // The only optional operation is Update. If Update is not implemented, | 80 | // The only optional operation is Update. If Update is not implemented, |
@@ -136,6 +155,27 @@ type Resource struct { | |||
136 | Timeouts *ResourceTimeout | 155 | Timeouts *ResourceTimeout |
137 | } | 156 | } |
138 | 157 | ||
158 | // ShimInstanceStateFromValue converts a cty.Value to a | ||
159 | // terraform.InstanceState. | ||
160 | func (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 | |||
139 | // See Resource documentation. | 179 | // See Resource documentation. |
140 | type CreateFunc func(*ResourceData, interface{}) error | 180 | type CreateFunc func(*ResourceData, interface{}) error |
141 | 181 | ||
@@ -155,6 +195,27 @@ type ExistsFunc func(*ResourceData, interface{}) (bool, error) | |||
155 | type StateMigrateFunc func( | 195 | type StateMigrateFunc func( |
156 | int, *terraform.InstanceState, interface{}) (*terraform.InstanceState, error) | 196 | int, *terraform.InstanceState, interface{}) (*terraform.InstanceState, error) |
157 | 197 | ||
198 | type 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 | ||
217 | type StateUpgradeFunc func(rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) | ||
218 | |||
158 | // See Resource documentation. | 219 | // See Resource documentation. |
159 | type CustomizeDiffFunc func(*ResourceDiff, interface{}) error | 220 | type CustomizeDiffFunc func(*ResourceDiff, interface{}) error |
160 | 221 | ||
@@ -247,7 +308,7 @@ func (r *Resource) Diff( | |||
247 | return nil, fmt.Errorf("[ERR] Error decoding timeout: %s", err) | 308 | return nil, fmt.Errorf("[ERR] Error decoding timeout: %s", err) |
248 | } | 309 | } |
249 | 310 | ||
250 | instanceDiff, err := schemaMap(r.Schema).Diff(s, c, r.CustomizeDiff, meta) | 311 | instanceDiff, err := schemaMap(r.Schema).Diff(s, c, r.CustomizeDiff, meta, true) |
251 | if err != nil { | 312 | if err != nil { |
252 | return instanceDiff, err | 313 | return instanceDiff, err |
253 | } | 314 | } |
@@ -263,6 +324,45 @@ func (r *Resource) Diff( | |||
263 | return instanceDiff, err | 324 | return instanceDiff, err |
264 | } | 325 | } |
265 | 326 | ||
327 | func (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 | |||
266 | // Validate validates the resource configuration against the schema. | 366 | // Validate validates the resource configuration against the schema. |
267 | func (r *Resource) Validate(c *terraform.ResourceConfig) ([]string, []error) { | 367 | func (r *Resource) Validate(c *terraform.ResourceConfig) ([]string, []error) { |
268 | warns, errs := schemaMap(r.Schema).Validate(c) | 368 | warns, errs := schemaMap(r.Schema).Validate(c) |
@@ -300,8 +400,11 @@ func (r *Resource) ReadDataApply( | |||
300 | return r.recordCurrentSchemaVersion(state), err | 400 | return r.recordCurrentSchemaVersion(state), err |
301 | } | 401 | } |
302 | 402 | ||
303 | // Refresh refreshes the state of the resource. | 403 | // RefreshWithoutUpgrade reads the instance state, but does not call |
304 | func (r *Resource) Refresh( | 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. | ||
407 | func (r *Resource) RefreshWithoutUpgrade( | ||
305 | s *terraform.InstanceState, | 408 | s *terraform.InstanceState, |
306 | meta interface{}) (*terraform.InstanceState, error) { | 409 | meta interface{}) (*terraform.InstanceState, error) { |
307 | // If the ID is already somehow blank, it doesn't exist | 410 | // If the ID is already somehow blank, it doesn't exist |
@@ -335,12 +438,60 @@ func (r *Resource) Refresh( | |||
335 | } | 438 | } |
336 | } | 439 | } |
337 | 440 | ||
338 | needsMigration, stateSchemaVersion := r.checkSchemaVersion(s) | 441 | data, err := schemaMap(r.Schema).Data(s, nil) |
339 | if needsMigration && r.MigrateState != nil { | 442 | data.timeouts = &rt |
340 | s, err := r.MigrateState(stateSchemaVersion, s, meta) | 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. | ||
457 | func (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 | |||
341 | if err != nil { | 478 | if err != nil { |
342 | return s, err | 479 | return s, err |
343 | } | 480 | } |
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 | ||
344 | } | 495 | } |
345 | 496 | ||
346 | data, err := schemaMap(r.Schema).Data(s, nil) | 497 | data, err := schemaMap(r.Schema).Data(s, nil) |
@@ -358,6 +509,71 @@ func (r *Resource) Refresh( | |||
358 | return r.recordCurrentSchemaVersion(state), err | 509 | return r.recordCurrentSchemaVersion(state), err |
359 | } | 510 | } |
360 | 511 | ||
512 | func (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 | |||
361 | // InternalValidate should be called to validate the structure | 577 | // InternalValidate should be called to validate the structure |
362 | // of the resource. | 578 | // of the resource. |
363 | // | 579 | // |
@@ -437,6 +653,31 @@ func (r *Resource) InternalValidate(topSchemaMap schemaMap, writable bool) error | |||
437 | } | 653 | } |
438 | } | 654 | } |
439 | 655 | ||
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 | |||
440 | // Data source | 681 | // Data source |
441 | if r.isTopLevel() && !writable { | 682 | if r.isTopLevel() && !writable { |
442 | tsm = schemaMap(r.Schema) | 683 | tsm = schemaMap(r.Schema) |
@@ -513,6 +754,13 @@ func (r *Resource) TestResourceData() *ResourceData { | |||
513 | } | 754 | } |
514 | } | 755 | } |
515 | 756 | ||
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. | ||
760 | func (r *Resource) SchemasForFlatmapPath(path string) []*Schema { | ||
761 | return SchemasForFlatmapPath(path, r.Schema) | ||
762 | } | ||
763 | |||
516 | // Returns true if the resource is "top level" i.e. not a sub-resource. | 764 | // Returns true if the resource is "top level" i.e. not a sub-resource. |
517 | func (r *Resource) isTopLevel() bool { | 765 | func (r *Resource) isTopLevel() bool { |
518 | // TODO: This is a heuristic; replace with a definitive attribute? | 766 | // TODO: This is a heuristic; replace with a definitive attribute? |
@@ -538,7 +786,15 @@ func (r *Resource) checkSchemaVersion(is *terraform.InstanceState) (bool, int) { | |||
538 | } | 786 | } |
539 | 787 | ||
540 | stateSchemaVersion, _ := strconv.Atoi(rawString) | 788 | stateSchemaVersion, _ := strconv.Atoi(rawString) |
541 | return stateSchemaVersion < r.SchemaVersion, stateSchemaVersion | 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 | ||
542 | } | 798 | } |
543 | 799 | ||
544 | func (r *Resource) recordCurrentSchemaVersion( | 800 | func (r *Resource) recordCurrentSchemaVersion( |