diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/helper/schema/schema.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/helper/schema/schema.go | 1537 |
1 files changed, 1537 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/schema.go b/vendor/github.com/hashicorp/terraform/helper/schema/schema.go new file mode 100644 index 0000000..32d1721 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/schema.go | |||
@@ -0,0 +1,1537 @@ | |||
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. | ||
12 | package schema | ||
13 | |||
14 | import ( | ||
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 | ||
27 | type 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. | ||
32 | type 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. | ||
199 | type 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. | ||
203 | type 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. | ||
208 | func 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. | ||
222 | func 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. | ||
235 | type 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. | ||
239 | type SchemaStateFunc func(interface{}) string | ||
240 | |||
241 | // SchemaValidateFunc is a function used to validate a single field in the | ||
242 | // schema. | ||
243 | type SchemaValidateFunc func(interface{}, string) ([]string, []error) | ||
244 | |||
245 | func (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. | ||
251 | func (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. | ||
268 | func (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 | |||
291 | func (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. | ||
354 | type schemaMap map[string]*Schema | ||
355 | |||
356 | // Data returns a ResourceData for the given schema, state, and diff. | ||
357 | // | ||
358 | // The diff is optional. | ||
359 | func (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. | ||
371 | func (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. | ||
479 | func (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. | ||
540 | func (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. | ||
547 | func (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 | |||
672 | func (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 | |||
709 | func (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 | |||
828 | func (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 | |||
922 | func (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 | |||
1043 | func (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 | |||
1099 | func (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 | |||
1113 | func (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 | |||
1153 | func (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 | |||
1172 | func (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 | |||
1243 | func (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 | |||
1311 | func 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 | |||
1346 | func 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 | |||
1372 | func (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 | |||
1416 | func (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 | |||
1484 | func (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. | ||
1514 | func (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 | } | ||