]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/terraform/helper/schema/schema.go
Merge branch 'master' of /Users/jake/terraform
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / helper / schema / schema.go
CommitLineData
bae9f6d2
JC
1// schema is a high-level framework for easily writing new providers
2// for Terraform. Usage of schema is recommended over attempting to write
3// to the low-level plugin interfaces manually.
4//
5// schema breaks down provider creation into simple CRUD operations for
6// resources. The logic of diffing, destroying before creating, updating
7// or creating, etc. is all handled by the framework. The plugin author
8// only needs to implement a configuration schema and the CRUD operations and
9// everything else is meant to just work.
10//
11// A good starting point is to view the Provider structure.
12package schema
13
14import (
15 "fmt"
16 "os"
17 "reflect"
18 "sort"
19 "strconv"
20 "strings"
21
22 "github.com/hashicorp/terraform/terraform"
23 "github.com/mitchellh/mapstructure"
24)
25
26// type used for schema package context keys
27type contextKey string
28
29// Schema is used to describe the structure of a value.
30//
31// Read the documentation of the struct elements for important details.
32type Schema struct {
33 // Type is the type of the value and must be one of the ValueType values.
34 //
35 // This type not only determines what type is expected/valid in configuring
36 // this value, but also what type is returned when ResourceData.Get is
37 // called. The types returned by Get are:
38 //
39 // TypeBool - bool
40 // TypeInt - int
41 // TypeFloat - float64
42 // TypeString - string
43 // TypeList - []interface{}
44 // TypeMap - map[string]interface{}
45 // TypeSet - *schema.Set
46 //
47 Type ValueType
48
49 // If one of these is set, then this item can come from the configuration.
50 // Both cannot be set. If Optional is set, the value is optional. If
51 // Required is set, the value is required.
52 //
53 // One of these must be set if the value is not computed. That is:
54 // value either comes from the config, is computed, or is both.
55 Optional bool
56 Required bool
57
58 // If this is non-nil, the provided function will be used during diff
59 // of this field. If this is nil, a default diff for the type of the
60 // schema will be used.
61 //
62 // This allows comparison based on something other than primitive, list
63 // or map equality - for example SSH public keys may be considered
64 // equivalent regardless of trailing whitespace.
65 DiffSuppressFunc SchemaDiffSuppressFunc
66
67 // If this is non-nil, then this will be a default value that is used
68 // when this item is not set in the configuration.
69 //
70 // DefaultFunc can be specified to compute a dynamic default.
71 // Only one of Default or DefaultFunc can be set. If DefaultFunc is
72 // used then its return value should be stable to avoid generating
73 // confusing/perpetual diffs.
74 //
75 // Changing either Default or the return value of DefaultFunc can be
76 // a breaking change, especially if the attribute in question has
77 // ForceNew set. If a default needs to change to align with changing
78 // assumptions in an upstream API then it may be necessary to also use
79 // the MigrateState function on the resource to change the state to match,
80 // or have the Read function adjust the state value to align with the
81 // new default.
82 //
83 // If Required is true above, then Default cannot be set. DefaultFunc
84 // can be set with Required. If the DefaultFunc returns nil, then there
85 // will be no default and the user will be asked to fill it in.
86 //
87 // If either of these is set, then the user won't be asked for input
88 // for this key if the default is not nil.
89 Default interface{}
90 DefaultFunc SchemaDefaultFunc
91
92 // Description is used as the description for docs or asking for user
93 // input. It should be relatively short (a few sentences max) and should
94 // be formatted to fit a CLI.
95 Description string
96
97 // InputDefault is the default value to use for when inputs are requested.
98 // This differs from Default in that if Default is set, no input is
99 // asked for. If Input is asked, this will be the default value offered.
100 InputDefault string
101
102 // The fields below relate to diffs.
103 //
104 // If Computed is true, then the result of this value is computed
105 // (unless specified by config) on creation.
106 //
107 // If ForceNew is true, then a change in this resource necessitates
108 // the creation of a new resource.
109 //
110 // StateFunc is a function called to change the value of this before
111 // storing it in the state (and likewise before comparing for diffs).
112 // The use for this is for example with large strings, you may want
113 // to simply store the hash of it.
114 Computed bool
115 ForceNew bool
116 StateFunc SchemaStateFunc
117
118 // The following fields are only set for a TypeList or TypeSet Type.
119 //
120 // Elem must be either a *Schema or a *Resource only if the Type is
121 // TypeList, and represents what the element type is. If it is *Schema,
122 // the element type is just a simple value. If it is *Resource, the
123 // element type is a complex structure, potentially with its own lifecycle.
124 //
125 // MaxItems defines a maximum amount of items that can exist within a
126 // TypeSet or TypeList. Specific use cases would be if a TypeSet is being
127 // used to wrap a complex structure, however more than one instance would
128 // cause instability.
129 //
130 // MinItems defines a minimum amount of items that can exist within a
131 // TypeSet or TypeList. Specific use cases would be if a TypeSet is being
132 // used to wrap a complex structure, however less than one instance would
133 // cause instability.
134 //
135 // PromoteSingle, if true, will allow single elements to be standalone
136 // and promote them to a list. For example "foo" would be promoted to
137 // ["foo"] automatically. This is primarily for legacy reasons and the
138 // ambiguity is not recommended for new usage. Promotion is only allowed
139 // for primitive element types.
140 Elem interface{}
141 MaxItems int
142 MinItems int
143 PromoteSingle bool
144
145 // The following fields are only valid for a TypeSet type.
146 //
147 // Set defines a function to determine the unique ID of an item so that
148 // a proper set can be built.
149 Set SchemaSetFunc
150
151 // ComputedWhen is a set of queries on the configuration. Whenever any
152 // of these things is changed, it will require a recompute (this requires
153 // that Computed is set to true).
154 //
155 // NOTE: This currently does not work.
156 ComputedWhen []string
157
158 // ConflictsWith is a set of schema keys that conflict with this schema.
159 // This will only check that they're set in the _config_. This will not
160 // raise an error for a malfunctioning resource that sets a conflicting
161 // key.
162 ConflictsWith []string
163
164 // When Deprecated is set, this attribute is deprecated.
165 //
166 // A deprecated field still works, but will probably stop working in near
167 // future. This string is the message shown to the user with instructions on
168 // how to address the deprecation.
169 Deprecated string
170
171 // When Removed is set, this attribute has been removed from the schema
172 //
173 // Removed attributes can be left in the Schema to generate informative error
174 // messages for the user when they show up in resource configurations.
175 // This string is the message shown to the user with instructions on
176 // what do to about the removed attribute.
177 Removed string
178
179 // ValidateFunc allows individual fields to define arbitrary validation
180 // logic. It is yielded the provided config value as an interface{} that is
181 // guaranteed to be of the proper Schema type, and it can yield warnings or
182 // errors based on inspection of that value.
183 //
184 // ValidateFunc currently only works for primitive types.
185 ValidateFunc SchemaValidateFunc
186
187 // Sensitive ensures that the attribute's value does not get displayed in
188 // logs or regular output. It should be used for passwords or other
189 // secret fields. Future versions of Terraform may encrypt these
190 // values.
191 Sensitive bool
192}
193
194// SchemaDiffSuppresFunc is a function which can be used to determine
195// whether a detected diff on a schema element is "valid" or not, and
196// suppress it from the plan if necessary.
197//
198// Return true if the diff should be suppressed, false to retain it.
199type SchemaDiffSuppressFunc func(k, old, new string, d *ResourceData) bool
200
201// SchemaDefaultFunc is a function called to return a default value for
202// a field.
203type SchemaDefaultFunc func() (interface{}, error)
204
205// EnvDefaultFunc is a helper function that returns the value of the
206// given environment variable, if one exists, or the default value
207// otherwise.
208func EnvDefaultFunc(k string, dv interface{}) SchemaDefaultFunc {
209 return func() (interface{}, error) {
210 if v := os.Getenv(k); v != "" {
211 return v, nil
212 }
213
214 return dv, nil
215 }
216}
217
218// MultiEnvDefaultFunc is a helper function that returns the value of the first
219// environment variable in the given list that returns a non-empty value. If
220// none of the environment variables return a value, the default value is
221// returned.
222func MultiEnvDefaultFunc(ks []string, dv interface{}) SchemaDefaultFunc {
223 return func() (interface{}, error) {
224 for _, k := range ks {
225 if v := os.Getenv(k); v != "" {
226 return v, nil
227 }
228 }
229 return dv, nil
230 }
231}
232
233// SchemaSetFunc is a function that must return a unique ID for the given
234// element. This unique ID is used to store the element in a hash.
235type SchemaSetFunc func(interface{}) int
236
237// SchemaStateFunc is a function used to convert some type to a string
238// to be stored in the state.
239type SchemaStateFunc func(interface{}) string
240
241// SchemaValidateFunc is a function used to validate a single field in the
242// schema.
243type SchemaValidateFunc func(interface{}, string) ([]string, []error)
244
245func (s *Schema) GoString() string {
246 return fmt.Sprintf("*%#v", *s)
247}
248
249// Returns a default value for this schema by either reading Default or
250// evaluating DefaultFunc. If neither of these are defined, returns nil.
251func (s *Schema) DefaultValue() (interface{}, error) {
252 if s.Default != nil {
253 return s.Default, nil
254 }
255
256 if s.DefaultFunc != nil {
257 defaultValue, err := s.DefaultFunc()
258 if err != nil {
259 return nil, fmt.Errorf("error loading default: %s", err)
260 }
261 return defaultValue, nil
262 }
263
264 return nil, nil
265}
266
267// Returns a zero value for the schema.
268func (s *Schema) ZeroValue() interface{} {
269 // If it's a set then we'll do a bit of extra work to provide the
270 // right hashing function in our empty value.
271 if s.Type == TypeSet {
272 setFunc := s.Set
273 if setFunc == nil {
274 // Default set function uses the schema to hash the whole value
275 elem := s.Elem
276 switch t := elem.(type) {
277 case *Schema:
278 setFunc = HashSchema(t)
279 case *Resource:
280 setFunc = HashResource(t)
281 default:
282 panic("invalid set element type")
283 }
284 }
285 return &Set{F: setFunc}
286 } else {
287 return s.Type.Zero()
288 }
289}
290
291func (s *Schema) finalizeDiff(
292 d *terraform.ResourceAttrDiff) *terraform.ResourceAttrDiff {
293 if d == nil {
294 return d
295 }
296
297 if s.Type == TypeBool {
298 normalizeBoolString := func(s string) string {
299 switch s {
300 case "0":
301 return "false"
302 case "1":
303 return "true"
304 }
305 return s
306 }
307 d.Old = normalizeBoolString(d.Old)
308 d.New = normalizeBoolString(d.New)
309 }
310
311 if s.Computed && !d.NewRemoved && d.New == "" {
312 // Computed attribute without a new value set
313 d.NewComputed = true
314 }
315
316 if s.ForceNew {
317 // ForceNew, mark that this field is requiring new under the
318 // following conditions, explained below:
319 //
320 // * Old != New - There is a change in value. This field
321 // is therefore causing a new resource.
322 //
323 // * NewComputed - This field is being computed, hence a
324 // potential change in value, mark as causing a new resource.
325 d.RequiresNew = d.Old != d.New || d.NewComputed
326 }
327
328 if d.NewRemoved {
329 return d
330 }
331
332 if s.Computed {
333 if d.Old != "" && d.New == "" {
334 // This is a computed value with an old value set already,
335 // just let it go.
336 return nil
337 }
338
339 if d.New == "" {
340 // Computed attribute without a new value set
341 d.NewComputed = true
342 }
343 }
344
345 if s.Sensitive {
346 // Set the Sensitive flag so output is hidden in the UI
347 d.Sensitive = true
348 }
349
350 return d
351}
352
353// schemaMap is a wrapper that adds nice functions on top of schemas.
354type schemaMap map[string]*Schema
355
356// Data returns a ResourceData for the given schema, state, and diff.
357//
358// The diff is optional.
359func (m schemaMap) Data(
360 s *terraform.InstanceState,
361 d *terraform.InstanceDiff) (*ResourceData, error) {
362 return &ResourceData{
363 schema: m,
364 state: s,
365 diff: d,
366 }, nil
367}
368
369// Diff returns the diff for a resource given the schema map,
370// state, and configuration.
371func (m schemaMap) Diff(
372 s *terraform.InstanceState,
373 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
374 result := new(terraform.InstanceDiff)
375 result.Attributes = make(map[string]*terraform.ResourceAttrDiff)
376
377 // Make sure to mark if the resource is tainted
378 if s != nil {
379 result.DestroyTainted = s.Tainted
380 }
381
382 d := &ResourceData{
383 schema: m,
384 state: s,
385 config: c,
386 }
387
388 for k, schema := range m {
389 err := m.diff(k, schema, result, d, false)
390 if err != nil {
391 return nil, err
392 }
393 }
394
395 // If the diff requires a new resource, then we recompute the diff
396 // so we have the complete new resource diff, and preserve the
397 // RequiresNew fields where necessary so the user knows exactly what
398 // caused that.
399 if result.RequiresNew() {
400 // Create the new diff
401 result2 := new(terraform.InstanceDiff)
402 result2.Attributes = make(map[string]*terraform.ResourceAttrDiff)
403
404 // Preserve the DestroyTainted flag
405 result2.DestroyTainted = result.DestroyTainted
406
407 // Reset the data to not contain state. We have to call init()
408 // again in order to reset the FieldReaders.
409 d.state = nil
410 d.init()
411
412 // Perform the diff again
413 for k, schema := range m {
414 err := m.diff(k, schema, result2, d, false)
415 if err != nil {
416 return nil, err
417 }
418 }
419
420 // Force all the fields to not force a new since we know what we
421 // want to force new.
422 for k, attr := range result2.Attributes {
423 if attr == nil {
424 continue
425 }
426
427 if attr.RequiresNew {
428 attr.RequiresNew = false
429 }
430
431 if s != nil {
432 attr.Old = s.Attributes[k]
433 }
434 }
435
436 // Now copy in all the requires new diffs...
437 for k, attr := range result.Attributes {
438 if attr == nil {
439 continue
440 }
441
442 newAttr, ok := result2.Attributes[k]
443 if !ok {
444 newAttr = attr
445 }
446
447 if attr.RequiresNew {
448 newAttr.RequiresNew = true
449 }
450
451 result2.Attributes[k] = newAttr
452 }
453
454 // And set the diff!
455 result = result2
456 }
457
458 // Remove any nil diffs just to keep things clean
459 for k, v := range result.Attributes {
460 if v == nil {
461 delete(result.Attributes, k)
462 }
463 }
464
465 // Go through and detect all of the ComputedWhens now that we've
466 // finished the diff.
467 // TODO
468
469 if result.Empty() {
470 // If we don't have any diff elements, just return nil
471 return nil, nil
472 }
473
474 return result, nil
475}
476
477// Input implements the terraform.ResourceProvider method by asking
478// for input for required configuration keys that don't have a value.
479func (m schemaMap) Input(
480 input terraform.UIInput,
481 c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) {
482 keys := make([]string, 0, len(m))
483 for k, _ := range m {
484 keys = append(keys, k)
485 }
486 sort.Strings(keys)
487
488 for _, k := range keys {
489 v := m[k]
490
491 // Skip things that don't require config, if that is even valid
492 // for a provider schema.
493 // Required XOR Optional must always be true to validate, so we only
494 // need to check one.
495 if v.Optional {
496 continue
497 }
498
499 // Deprecated fields should never prompt
500 if v.Deprecated != "" {
501 continue
502 }
503
504 // Skip things that have a value of some sort already
505 if _, ok := c.Raw[k]; ok {
506 continue
507 }
508
509 // Skip if it has a default value
510 defaultValue, err := v.DefaultValue()
511 if err != nil {
512 return nil, fmt.Errorf("%s: error loading default: %s", k, err)
513 }
514 if defaultValue != nil {
515 continue
516 }
517
518 var value interface{}
519 switch v.Type {
520 case TypeBool, TypeInt, TypeFloat, TypeSet, TypeList:
521 continue
522 case TypeString:
523 value, err = m.inputString(input, k, v)
524 default:
525 panic(fmt.Sprintf("Unknown type for input: %#v", v.Type))
526 }
527
528 if err != nil {
529 return nil, fmt.Errorf(
530 "%s: %s", k, err)
531 }
532
533 c.Config[k] = value
534 }
535
536 return c, nil
537}
538
539// Validate validates the configuration against this schema mapping.
540func (m schemaMap) Validate(c *terraform.ResourceConfig) ([]string, []error) {
541 return m.validateObject("", m, c)
542}
543
544// InternalValidate validates the format of this schema. This should be called
545// from a unit test (and not in user-path code) to verify that a schema
546// is properly built.
547func (m schemaMap) InternalValidate(topSchemaMap schemaMap) error {
548 if topSchemaMap == nil {
549 topSchemaMap = m
550 }
551 for k, v := range m {
552 if v.Type == TypeInvalid {
553 return fmt.Errorf("%s: Type must be specified", k)
554 }
555
556 if v.Optional && v.Required {
557 return fmt.Errorf("%s: Optional or Required must be set, not both", k)
558 }
559
560 if v.Required && v.Computed {
561 return fmt.Errorf("%s: Cannot be both Required and Computed", k)
562 }
563
564 if !v.Required && !v.Optional && !v.Computed {
565 return fmt.Errorf("%s: One of optional, required, or computed must be set", k)
566 }
567
568 if v.Computed && v.Default != nil {
569 return fmt.Errorf("%s: Default must be nil if computed", k)
570 }
571
572 if v.Required && v.Default != nil {
573 return fmt.Errorf("%s: Default cannot be set with Required", k)
574 }
575
576 if len(v.ComputedWhen) > 0 && !v.Computed {
577 return fmt.Errorf("%s: ComputedWhen can only be set with Computed", k)
578 }
579
580 if len(v.ConflictsWith) > 0 && v.Required {
581 return fmt.Errorf("%s: ConflictsWith cannot be set with Required", k)
582 }
583
584 if len(v.ConflictsWith) > 0 {
585 for _, key := range v.ConflictsWith {
586 parts := strings.Split(key, ".")
587 sm := topSchemaMap
588 var target *Schema
589 for _, part := range parts {
590 // Skip index fields
591 if _, err := strconv.Atoi(part); err == nil {
592 continue
593 }
594
595 var ok bool
596 if target, ok = sm[part]; !ok {
597 return fmt.Errorf("%s: ConflictsWith references unknown attribute (%s)", k, key)
598 }
599
600 if subResource, ok := target.Elem.(*Resource); ok {
601 sm = schemaMap(subResource.Schema)
602 }
603 }
604 if target == nil {
605 return fmt.Errorf("%s: ConflictsWith cannot find target attribute (%s), sm: %#v", k, key, sm)
606 }
607 if target.Required {
608 return fmt.Errorf("%s: ConflictsWith cannot contain Required attribute (%s)", k, key)
609 }
610
611 if len(target.ComputedWhen) > 0 {
612 return fmt.Errorf("%s: ConflictsWith cannot contain Computed(When) attribute (%s)", k, key)
613 }
614 }
615 }
616
617 if v.Type == TypeList || v.Type == TypeSet {
618 if v.Elem == nil {
619 return fmt.Errorf("%s: Elem must be set for lists", k)
620 }
621
622 if v.Default != nil {
623 return fmt.Errorf("%s: Default is not valid for lists or sets", k)
624 }
625
626 if v.Type != TypeSet && v.Set != nil {
627 return fmt.Errorf("%s: Set can only be set for TypeSet", k)
628 }
629
630 switch t := v.Elem.(type) {
631 case *Resource:
632 if err := t.InternalValidate(topSchemaMap, true); err != nil {
633 return err
634 }
635 case *Schema:
636 bad := t.Computed || t.Optional || t.Required
637 if bad {
638 return fmt.Errorf(
639 "%s: Elem must have only Type set", k)
640 }
641 }
642 } else {
643 if v.MaxItems > 0 || v.MinItems > 0 {
644 return fmt.Errorf("%s: MaxItems and MinItems are only supported on lists or sets", k)
645 }
646 }
647
648 // Computed-only field
649 if v.Computed && !v.Optional {
650 if v.ValidateFunc != nil {
651 return fmt.Errorf("%s: ValidateFunc is for validating user input, "+
652 "there's nothing to validate on computed-only field", k)
653 }
654 if v.DiffSuppressFunc != nil {
655 return fmt.Errorf("%s: DiffSuppressFunc is for suppressing differences"+
656 " between config and state representation. "+
657 "There is no config for computed-only field, nothing to compare.", k)
658 }
659 }
660
661 if v.ValidateFunc != nil {
662 switch v.Type {
663 case TypeList, TypeSet:
664 return fmt.Errorf("ValidateFunc is not yet supported on lists or sets.")
665 }
666 }
667 }
668
669 return nil
670}
671
672func (m schemaMap) diff(
673 k string,
674 schema *Schema,
675 diff *terraform.InstanceDiff,
676 d *ResourceData,
677 all bool) error {
678
679 unsupressedDiff := new(terraform.InstanceDiff)
680 unsupressedDiff.Attributes = make(map[string]*terraform.ResourceAttrDiff)
681
682 var err error
683 switch schema.Type {
684 case TypeBool, TypeInt, TypeFloat, TypeString:
685 err = m.diffString(k, schema, unsupressedDiff, d, all)
686 case TypeList:
687 err = m.diffList(k, schema, unsupressedDiff, d, all)
688 case TypeMap:
689 err = m.diffMap(k, schema, unsupressedDiff, d, all)
690 case TypeSet:
691 err = m.diffSet(k, schema, unsupressedDiff, d, all)
692 default:
693 err = fmt.Errorf("%s: unknown type %#v", k, schema.Type)
694 }
695
696 for attrK, attrV := range unsupressedDiff.Attributes {
697 if schema.DiffSuppressFunc != nil &&
698 attrV != nil &&
699 schema.DiffSuppressFunc(attrK, attrV.Old, attrV.New, d) {
700 continue
701 }
702
703 diff.Attributes[attrK] = attrV
704 }
705
706 return err
707}
708
709func (m schemaMap) diffList(
710 k string,
711 schema *Schema,
712 diff *terraform.InstanceDiff,
713 d *ResourceData,
714 all bool) error {
715 o, n, _, computedList := d.diffChange(k)
716 if computedList {
717 n = nil
718 }
719 nSet := n != nil
720
721 // If we have an old value and no new value is set or will be
722 // computed once all variables can be interpolated and we're
723 // computed, then nothing has changed.
724 if o != nil && n == nil && !computedList && schema.Computed {
725 return nil
726 }
727
728 if o == nil {
729 o = []interface{}{}
730 }
731 if n == nil {
732 n = []interface{}{}
733 }
734 if s, ok := o.(*Set); ok {
735 o = s.List()
736 }
737 if s, ok := n.(*Set); ok {
738 n = s.List()
739 }
740 os := o.([]interface{})
741 vs := n.([]interface{})
742
743 // If the new value was set, and the two are equal, then we're done.
744 // We have to do this check here because sets might be NOT
745 // reflect.DeepEqual so we need to wait until we get the []interface{}
746 if !all && nSet && reflect.DeepEqual(os, vs) {
747 return nil
748 }
749
750 // Get the counts
751 oldLen := len(os)
752 newLen := len(vs)
753 oldStr := strconv.FormatInt(int64(oldLen), 10)
754
755 // If the whole list is computed, then say that the # is computed
756 if computedList {
757 diff.Attributes[k+".#"] = &terraform.ResourceAttrDiff{
758 Old: oldStr,
759 NewComputed: true,
760 RequiresNew: schema.ForceNew,
761 }
762 return nil
763 }
764
765 // If the counts are not the same, then record that diff
766 changed := oldLen != newLen
767 computed := oldLen == 0 && newLen == 0 && schema.Computed
768 if changed || computed || all {
769 countSchema := &Schema{
770 Type: TypeInt,
771 Computed: schema.Computed,
772 ForceNew: schema.ForceNew,
773 }
774
775 newStr := ""
776 if !computed {
777 newStr = strconv.FormatInt(int64(newLen), 10)
778 } else {
779 oldStr = ""
780 }
781
782 diff.Attributes[k+".#"] = countSchema.finalizeDiff(&terraform.ResourceAttrDiff{
783 Old: oldStr,
784 New: newStr,
785 })
786 }
787
788 // Figure out the maximum
789 maxLen := oldLen
790 if newLen > maxLen {
791 maxLen = newLen
792 }
793
794 switch t := schema.Elem.(type) {
795 case *Resource:
796 // This is a complex resource
797 for i := 0; i < maxLen; i++ {
798 for k2, schema := range t.Schema {
799 subK := fmt.Sprintf("%s.%d.%s", k, i, k2)
800 err := m.diff(subK, schema, diff, d, all)
801 if err != nil {
802 return err
803 }
804 }
805 }
806 case *Schema:
807 // Copy the schema so that we can set Computed/ForceNew from
808 // the parent schema (the TypeList).
809 t2 := *t
810 t2.ForceNew = schema.ForceNew
811
812 // This is just a primitive element, so go through each and
813 // just diff each.
814 for i := 0; i < maxLen; i++ {
815 subK := fmt.Sprintf("%s.%d", k, i)
816 err := m.diff(subK, &t2, diff, d, all)
817 if err != nil {
818 return err
819 }
820 }
821 default:
822 return fmt.Errorf("%s: unknown element type (internal)", k)
823 }
824
825 return nil
826}
827
828func (m schemaMap) diffMap(
829 k string,
830 schema *Schema,
831 diff *terraform.InstanceDiff,
832 d *ResourceData,
833 all bool) error {
834 prefix := k + "."
835
836 // First get all the values from the state
837 var stateMap, configMap map[string]string
838 o, n, _, nComputed := d.diffChange(k)
839 if err := mapstructure.WeakDecode(o, &stateMap); err != nil {
840 return fmt.Errorf("%s: %s", k, err)
841 }
842 if err := mapstructure.WeakDecode(n, &configMap); err != nil {
843 return fmt.Errorf("%s: %s", k, err)
844 }
845
846 // Keep track of whether the state _exists_ at all prior to clearing it
847 stateExists := o != nil
848
849 // Delete any count values, since we don't use those
850 delete(configMap, "%")
851 delete(stateMap, "%")
852
853 // Check if the number of elements has changed.
854 oldLen, newLen := len(stateMap), len(configMap)
855 changed := oldLen != newLen
856 if oldLen != 0 && newLen == 0 && schema.Computed {
857 changed = false
858 }
859
860 // It is computed if we have no old value, no new value, the schema
861 // says it is computed, and it didn't exist in the state before. The
862 // last point means: if it existed in the state, even empty, then it
863 // has already been computed.
864 computed := oldLen == 0 && newLen == 0 && schema.Computed && !stateExists
865
866 // If the count has changed or we're computed, then add a diff for the
867 // count. "nComputed" means that the new value _contains_ a value that
868 // is computed. We don't do granular diffs for this yet, so we mark the
869 // whole map as computed.
870 if changed || computed || nComputed {
871 countSchema := &Schema{
872 Type: TypeInt,
873 Computed: schema.Computed || nComputed,
874 ForceNew: schema.ForceNew,
875 }
876
877 oldStr := strconv.FormatInt(int64(oldLen), 10)
878 newStr := ""
879 if !computed && !nComputed {
880 newStr = strconv.FormatInt(int64(newLen), 10)
881 } else {
882 oldStr = ""
883 }
884
885 diff.Attributes[k+".%"] = countSchema.finalizeDiff(
886 &terraform.ResourceAttrDiff{
887 Old: oldStr,
888 New: newStr,
889 },
890 )
891 }
892
893 // If the new map is nil and we're computed, then ignore it.
894 if n == nil && schema.Computed {
895 return nil
896 }
897
898 // Now we compare, preferring values from the config map
899 for k, v := range configMap {
900 old, ok := stateMap[k]
901 delete(stateMap, k)
902
903 if old == v && ok && !all {
904 continue
905 }
906
907 diff.Attributes[prefix+k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{
908 Old: old,
909 New: v,
910 })
911 }
912 for k, v := range stateMap {
913 diff.Attributes[prefix+k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{
914 Old: v,
915 NewRemoved: true,
916 })
917 }
918
919 return nil
920}
921
922func (m schemaMap) diffSet(
923 k string,
924 schema *Schema,
925 diff *terraform.InstanceDiff,
926 d *ResourceData,
927 all bool) error {
928
929 o, n, _, computedSet := d.diffChange(k)
930 if computedSet {
931 n = nil
932 }
933 nSet := n != nil
934
935 // If we have an old value and no new value is set or will be
936 // computed once all variables can be interpolated and we're
937 // computed, then nothing has changed.
938 if o != nil && n == nil && !computedSet && schema.Computed {
939 return nil
940 }
941
942 if o == nil {
943 o = schema.ZeroValue().(*Set)
944 }
945 if n == nil {
946 n = schema.ZeroValue().(*Set)
947 }
948 os := o.(*Set)
949 ns := n.(*Set)
950
951 // If the new value was set, compare the listCode's to determine if
952 // the two are equal. Comparing listCode's instead of the actual values
953 // is needed because there could be computed values in the set which
954 // would result in false positives while comparing.
955 if !all && nSet && reflect.DeepEqual(os.listCode(), ns.listCode()) {
956 return nil
957 }
958
959 // Get the counts
960 oldLen := os.Len()
961 newLen := ns.Len()
962 oldStr := strconv.Itoa(oldLen)
963 newStr := strconv.Itoa(newLen)
964
965 // Build a schema for our count
966 countSchema := &Schema{
967 Type: TypeInt,
968 Computed: schema.Computed,
969 ForceNew: schema.ForceNew,
970 }
971
972 // If the set computed then say that the # is computed
973 if computedSet || schema.Computed && !nSet {
974 // If # already exists, equals 0 and no new set is supplied, there
975 // is nothing to record in the diff
976 count, ok := d.GetOk(k + ".#")
977 if ok && count.(int) == 0 && !nSet && !computedSet {
978 return nil
979 }
980
981 // Set the count but make sure that if # does not exist, we don't
982 // use the zeroed value
983 countStr := strconv.Itoa(count.(int))
984 if !ok {
985 countStr = ""
986 }
987
988 diff.Attributes[k+".#"] = countSchema.finalizeDiff(&terraform.ResourceAttrDiff{
989 Old: countStr,
990 NewComputed: true,
991 })
992 return nil
993 }
994
995 // If the counts are not the same, then record that diff
996 changed := oldLen != newLen
997 if changed || all {
998 diff.Attributes[k+".#"] = countSchema.finalizeDiff(&terraform.ResourceAttrDiff{
999 Old: oldStr,
1000 New: newStr,
1001 })
1002 }
1003
1004 // Build the list of codes that will make up our set. This is the
1005 // removed codes as well as all the codes in the new codes.
1006 codes := make([][]string, 2)
1007 codes[0] = os.Difference(ns).listCode()
1008 codes[1] = ns.listCode()
1009 for _, list := range codes {
1010 for _, code := range list {
1011 switch t := schema.Elem.(type) {
1012 case *Resource:
1013 // This is a complex resource
1014 for k2, schema := range t.Schema {
1015 subK := fmt.Sprintf("%s.%s.%s", k, code, k2)
1016 err := m.diff(subK, schema, diff, d, true)
1017 if err != nil {
1018 return err
1019 }
1020 }
1021 case *Schema:
1022 // Copy the schema so that we can set Computed/ForceNew from
1023 // the parent schema (the TypeSet).
1024 t2 := *t
1025 t2.ForceNew = schema.ForceNew
1026
1027 // This is just a primitive element, so go through each and
1028 // just diff each.
1029 subK := fmt.Sprintf("%s.%s", k, code)
1030 err := m.diff(subK, &t2, diff, d, true)
1031 if err != nil {
1032 return err
1033 }
1034 default:
1035 return fmt.Errorf("%s: unknown element type (internal)", k)
1036 }
1037 }
1038 }
1039
1040 return nil
1041}
1042
1043func (m schemaMap) diffString(
1044 k string,
1045 schema *Schema,
1046 diff *terraform.InstanceDiff,
1047 d *ResourceData,
1048 all bool) error {
1049 var originalN interface{}
1050 var os, ns string
1051 o, n, _, computed := d.diffChange(k)
1052 if schema.StateFunc != nil && n != nil {
1053 originalN = n
1054 n = schema.StateFunc(n)
1055 }
1056 nraw := n
1057 if nraw == nil && o != nil {
1058 nraw = schema.Type.Zero()
1059 }
1060 if err := mapstructure.WeakDecode(o, &os); err != nil {
1061 return fmt.Errorf("%s: %s", k, err)
1062 }
1063 if err := mapstructure.WeakDecode(nraw, &ns); err != nil {
1064 return fmt.Errorf("%s: %s", k, err)
1065 }
1066
1067 if os == ns && !all {
1068 // They're the same value. If there old value is not blank or we
1069 // have an ID, then return right away since we're already setup.
1070 if os != "" || d.Id() != "" {
1071 return nil
1072 }
1073
1074 // Otherwise, only continue if we're computed
1075 if !schema.Computed && !computed {
1076 return nil
1077 }
1078 }
1079
1080 removed := false
1081 if o != nil && n == nil {
1082 removed = true
1083 }
1084 if removed && schema.Computed {
1085 return nil
1086 }
1087
1088 diff.Attributes[k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{
1089 Old: os,
1090 New: ns,
1091 NewExtra: originalN,
1092 NewRemoved: removed,
1093 NewComputed: computed,
1094 })
1095
1096 return nil
1097}
1098
1099func (m schemaMap) inputString(
1100 input terraform.UIInput,
1101 k string,
1102 schema *Schema) (interface{}, error) {
1103 result, err := input.Input(&terraform.InputOpts{
1104 Id: k,
1105 Query: k,
1106 Description: schema.Description,
1107 Default: schema.InputDefault,
1108 })
1109
1110 return result, err
1111}
1112
1113func (m schemaMap) validate(
1114 k string,
1115 schema *Schema,
1116 c *terraform.ResourceConfig) ([]string, []error) {
1117 raw, ok := c.Get(k)
1118 if !ok && schema.DefaultFunc != nil {
1119 // We have a dynamic default. Check if we have a value.
1120 var err error
1121 raw, err = schema.DefaultFunc()
1122 if err != nil {
1123 return nil, []error{fmt.Errorf(
1124 "%q, error loading default: %s", k, err)}
1125 }
1126
1127 // We're okay as long as we had a value set
1128 ok = raw != nil
1129 }
1130 if !ok {
1131 if schema.Required {
1132 return nil, []error{fmt.Errorf(
1133 "%q: required field is not set", k)}
1134 }
1135
1136 return nil, nil
1137 }
1138
1139 if !schema.Required && !schema.Optional {
1140 // This is a computed-only field
1141 return nil, []error{fmt.Errorf(
1142 "%q: this field cannot be set", k)}
1143 }
1144
1145 err := m.validateConflictingAttributes(k, schema, c)
1146 if err != nil {
1147 return nil, []error{err}
1148 }
1149
1150 return m.validateType(k, raw, schema, c)
1151}
1152
1153func (m schemaMap) validateConflictingAttributes(
1154 k string,
1155 schema *Schema,
1156 c *terraform.ResourceConfig) error {
1157
1158 if len(schema.ConflictsWith) == 0 {
1159 return nil
1160 }
1161
1162 for _, conflicting_key := range schema.ConflictsWith {
1163 if value, ok := c.Get(conflicting_key); ok {
1164 return fmt.Errorf(
1165 "%q: conflicts with %s (%#v)", k, conflicting_key, value)
1166 }
1167 }
1168
1169 return nil
1170}
1171
1172func (m schemaMap) validateList(
1173 k string,
1174 raw interface{},
1175 schema *Schema,
1176 c *terraform.ResourceConfig) ([]string, []error) {
1177 // We use reflection to verify the slice because you can't
1178 // case to []interface{} unless the slice is exactly that type.
1179 rawV := reflect.ValueOf(raw)
1180
1181 // If we support promotion and the raw value isn't a slice, wrap
1182 // it in []interface{} and check again.
1183 if schema.PromoteSingle && rawV.Kind() != reflect.Slice {
1184 raw = []interface{}{raw}
1185 rawV = reflect.ValueOf(raw)
1186 }
1187
1188 if rawV.Kind() != reflect.Slice {
1189 return nil, []error{fmt.Errorf(
1190 "%s: should be a list", k)}
1191 }
1192
1193 // Validate length
1194 if schema.MaxItems > 0 && rawV.Len() > schema.MaxItems {
1195 return nil, []error{fmt.Errorf(
1196 "%s: attribute supports %d item maximum, config has %d declared", k, schema.MaxItems, rawV.Len())}
1197 }
1198
1199 if schema.MinItems > 0 && rawV.Len() < schema.MinItems {
1200 return nil, []error{fmt.Errorf(
1201 "%s: attribute supports %d item as a minimum, config has %d declared", k, schema.MinItems, rawV.Len())}
1202 }
1203
1204 // Now build the []interface{}
1205 raws := make([]interface{}, rawV.Len())
1206 for i, _ := range raws {
1207 raws[i] = rawV.Index(i).Interface()
1208 }
1209
1210 var ws []string
1211 var es []error
1212 for i, raw := range raws {
1213 key := fmt.Sprintf("%s.%d", k, i)
1214
1215 // Reify the key value from the ResourceConfig.
1216 // If the list was computed we have all raw values, but some of these
1217 // may be known in the config, and aren't individually marked as Computed.
1218 if r, ok := c.Get(key); ok {
1219 raw = r
1220 }
1221
1222 var ws2 []string
1223 var es2 []error
1224 switch t := schema.Elem.(type) {
1225 case *Resource:
1226 // This is a sub-resource
1227 ws2, es2 = m.validateObject(key, t.Schema, c)
1228 case *Schema:
1229 ws2, es2 = m.validateType(key, raw, t, c)
1230 }
1231
1232 if len(ws2) > 0 {
1233 ws = append(ws, ws2...)
1234 }
1235 if len(es2) > 0 {
1236 es = append(es, es2...)
1237 }
1238 }
1239
1240 return ws, es
1241}
1242
1243func (m schemaMap) validateMap(
1244 k string,
1245 raw interface{},
1246 schema *Schema,
1247 c *terraform.ResourceConfig) ([]string, []error) {
1248 // We use reflection to verify the slice because you can't
1249 // case to []interface{} unless the slice is exactly that type.
1250 rawV := reflect.ValueOf(raw)
1251 switch rawV.Kind() {
1252 case reflect.String:
1253 // If raw and reified are equal, this is a string and should
1254 // be rejected.
1255 reified, reifiedOk := c.Get(k)
1256 if reifiedOk && raw == reified && !c.IsComputed(k) {
1257 return nil, []error{fmt.Errorf("%s: should be a map", k)}
1258 }
1259 // Otherwise it's likely raw is an interpolation.
1260 return nil, nil
1261 case reflect.Map:
1262 case reflect.Slice:
1263 default:
1264 return nil, []error{fmt.Errorf("%s: should be a map", k)}
1265 }
1266
1267 // If it is not a slice, validate directly
1268 if rawV.Kind() != reflect.Slice {
1269 mapIface := rawV.Interface()
1270 if _, errs := validateMapValues(k, mapIface.(map[string]interface{}), schema); len(errs) > 0 {
1271 return nil, errs
1272 }
1273 if schema.ValidateFunc != nil {
1274 return schema.ValidateFunc(mapIface, k)
1275 }
1276 return nil, nil
1277 }
1278
1279 // It is a slice, verify that all the elements are maps
1280 raws := make([]interface{}, rawV.Len())
1281 for i, _ := range raws {
1282 raws[i] = rawV.Index(i).Interface()
1283 }
1284
1285 for _, raw := range raws {
1286 v := reflect.ValueOf(raw)
1287 if v.Kind() != reflect.Map {
1288 return nil, []error{fmt.Errorf(
1289 "%s: should be a map", k)}
1290 }
1291 mapIface := v.Interface()
1292 if _, errs := validateMapValues(k, mapIface.(map[string]interface{}), schema); len(errs) > 0 {
1293 return nil, errs
1294 }
1295 }
1296
1297 if schema.ValidateFunc != nil {
1298 validatableMap := make(map[string]interface{})
1299 for _, raw := range raws {
1300 for k, v := range raw.(map[string]interface{}) {
1301 validatableMap[k] = v
1302 }
1303 }
1304
1305 return schema.ValidateFunc(validatableMap, k)
1306 }
1307
1308 return nil, nil
1309}
1310
1311func validateMapValues(k string, m map[string]interface{}, schema *Schema) ([]string, []error) {
1312 for key, raw := range m {
1313 valueType, err := getValueType(k, schema)
1314 if err != nil {
1315 return nil, []error{err}
1316 }
1317
1318 switch valueType {
1319 case TypeBool:
1320 var n bool
1321 if err := mapstructure.WeakDecode(raw, &n); err != nil {
1322 return nil, []error{fmt.Errorf("%s (%s): %s", k, key, err)}
1323 }
1324 case TypeInt:
1325 var n int
1326 if err := mapstructure.WeakDecode(raw, &n); err != nil {
1327 return nil, []error{fmt.Errorf("%s (%s): %s", k, key, err)}
1328 }
1329 case TypeFloat:
1330 var n float64
1331 if err := mapstructure.WeakDecode(raw, &n); err != nil {
1332 return nil, []error{fmt.Errorf("%s (%s): %s", k, key, err)}
1333 }
1334 case TypeString:
1335 var n string
1336 if err := mapstructure.WeakDecode(raw, &n); err != nil {
1337 return nil, []error{fmt.Errorf("%s (%s): %s", k, key, err)}
1338 }
1339 default:
1340 panic(fmt.Sprintf("Unknown validation type: %#v", schema.Type))
1341 }
1342 }
1343 return nil, nil
1344}
1345
1346func getValueType(k string, schema *Schema) (ValueType, error) {
1347 if schema.Elem == nil {
1348 return TypeString, nil
1349 }
1350 if vt, ok := schema.Elem.(ValueType); ok {
1351 return vt, nil
1352 }
1353
1354 if s, ok := schema.Elem.(*Schema); ok {
1355 if s.Elem == nil {
1356 return TypeString, nil
1357 }
1358 if vt, ok := s.Elem.(ValueType); ok {
1359 return vt, nil
1360 }
1361 }
1362
1363 if _, ok := schema.Elem.(*Resource); ok {
1364 // TODO: We don't actually support this (yet)
1365 // but silently pass the validation, until we decide
1366 // how to handle nested structures in maps
1367 return TypeString, nil
1368 }
1369 return 0, fmt.Errorf("%s: unexpected map value type: %#v", k, schema.Elem)
1370}
1371
1372func (m schemaMap) validateObject(
1373 k string,
1374 schema map[string]*Schema,
1375 c *terraform.ResourceConfig) ([]string, []error) {
1376 raw, _ := c.GetRaw(k)
1377 if _, ok := raw.(map[string]interface{}); !ok {
1378 return nil, []error{fmt.Errorf(
1379 "%s: expected object, got %s",
1380 k, reflect.ValueOf(raw).Kind())}
1381 }
1382
1383 var ws []string
1384 var es []error
1385 for subK, s := range schema {
1386 key := subK
1387 if k != "" {
1388 key = fmt.Sprintf("%s.%s", k, subK)
1389 }
1390
1391 ws2, es2 := m.validate(key, s, c)
1392 if len(ws2) > 0 {
1393 ws = append(ws, ws2...)
1394 }
1395 if len(es2) > 0 {
1396 es = append(es, es2...)
1397 }
1398 }
1399
1400 // Detect any extra/unknown keys and report those as errors.
1401 if m, ok := raw.(map[string]interface{}); ok {
1402 for subk, _ := range m {
1403 if _, ok := schema[subk]; !ok {
1404 if subk == TimeoutsConfigKey {
1405 continue
1406 }
1407 es = append(es, fmt.Errorf(
1408 "%s: invalid or unknown key: %s", k, subk))
1409 }
1410 }
1411 }
1412
1413 return ws, es
1414}
1415
1416func (m schemaMap) validatePrimitive(
1417 k string,
1418 raw interface{},
1419 schema *Schema,
1420 c *terraform.ResourceConfig) ([]string, []error) {
1421
1422 // Catch if the user gave a complex type where a primitive was
1423 // expected, so we can return a friendly error message that
1424 // doesn't contain Go type system terminology.
1425 switch reflect.ValueOf(raw).Type().Kind() {
1426 case reflect.Slice:
1427 return nil, []error{
1428 fmt.Errorf("%s must be a single value, not a list", k),
1429 }
1430 case reflect.Map:
1431 return nil, []error{
1432 fmt.Errorf("%s must be a single value, not a map", k),
1433 }
1434 default: // ok
1435 }
1436
1437 if c.IsComputed(k) {
1438 // If the key is being computed, then it is not an error as
1439 // long as it's not a slice or map.
1440 return nil, nil
1441 }
1442
1443 var decoded interface{}
1444 switch schema.Type {
1445 case TypeBool:
1446 // Verify that we can parse this as the correct type
1447 var n bool
1448 if err := mapstructure.WeakDecode(raw, &n); err != nil {
1449 return nil, []error{fmt.Errorf("%s: %s", k, err)}
1450 }
1451 decoded = n
1452 case TypeInt:
1453 // Verify that we can parse this as an int
1454 var n int
1455 if err := mapstructure.WeakDecode(raw, &n); err != nil {
1456 return nil, []error{fmt.Errorf("%s: %s", k, err)}
1457 }
1458 decoded = n
1459 case TypeFloat:
1460 // Verify that we can parse this as an int
1461 var n float64
1462 if err := mapstructure.WeakDecode(raw, &n); err != nil {
1463 return nil, []error{fmt.Errorf("%s: %s", k, err)}
1464 }
1465 decoded = n
1466 case TypeString:
1467 // Verify that we can parse this as a string
1468 var n string
1469 if err := mapstructure.WeakDecode(raw, &n); err != nil {
1470 return nil, []error{fmt.Errorf("%s: %s", k, err)}
1471 }
1472 decoded = n
1473 default:
1474 panic(fmt.Sprintf("Unknown validation type: %#v", schema.Type))
1475 }
1476
1477 if schema.ValidateFunc != nil {
1478 return schema.ValidateFunc(decoded, k)
1479 }
1480
1481 return nil, nil
1482}
1483
1484func (m schemaMap) validateType(
1485 k string,
1486 raw interface{},
1487 schema *Schema,
1488 c *terraform.ResourceConfig) ([]string, []error) {
1489 var ws []string
1490 var es []error
1491 switch schema.Type {
1492 case TypeSet, TypeList:
1493 ws, es = m.validateList(k, raw, schema, c)
1494 case TypeMap:
1495 ws, es = m.validateMap(k, raw, schema, c)
1496 default:
1497 ws, es = m.validatePrimitive(k, raw, schema, c)
1498 }
1499
1500 if schema.Deprecated != "" {
1501 ws = append(ws, fmt.Sprintf(
1502 "%q: [DEPRECATED] %s", k, schema.Deprecated))
1503 }
1504
1505 if schema.Removed != "" {
1506 es = append(es, fmt.Errorf(
1507 "%q: [REMOVED] %s", k, schema.Removed))
1508 }
1509
1510 return ws, es
1511}
1512
1513// Zero returns the zero value for a type.
1514func (t ValueType) Zero() interface{} {
1515 switch t {
1516 case TypeInvalid:
1517 return nil
1518 case TypeBool:
1519 return false
1520 case TypeInt:
1521 return 0
1522 case TypeFloat:
1523 return 0.0
1524 case TypeString:
1525 return ""
1526 case TypeList:
1527 return []interface{}{}
1528 case TypeMap:
1529 return map[string]interface{}{}
1530 case TypeSet:
1531 return new(Set)
1532 case typeObject:
1533 return map[string]interface{}{}
1534 default:
1535 panic(fmt.Sprintf("unknown type %s", t))
1536 }
1537}