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