diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/config/config.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/config/config.go | 1096 |
1 files changed, 1096 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/config/config.go b/vendor/github.com/hashicorp/terraform/config/config.go new file mode 100644 index 0000000..9a764ac --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/config/config.go | |||
@@ -0,0 +1,1096 @@ | |||
1 | // The config package is responsible for loading and validating the | ||
2 | // configuration. | ||
3 | package config | ||
4 | |||
5 | import ( | ||
6 | "fmt" | ||
7 | "regexp" | ||
8 | "strconv" | ||
9 | "strings" | ||
10 | |||
11 | "github.com/hashicorp/go-multierror" | ||
12 | "github.com/hashicorp/hil" | ||
13 | "github.com/hashicorp/hil/ast" | ||
14 | "github.com/hashicorp/terraform/helper/hilmapstructure" | ||
15 | "github.com/mitchellh/reflectwalk" | ||
16 | ) | ||
17 | |||
18 | // NameRegexp is the regular expression that all names (modules, providers, | ||
19 | // resources, etc.) must follow. | ||
20 | var NameRegexp = regexp.MustCompile(`(?i)\A[A-Z0-9_][A-Z0-9\-\_]*\z`) | ||
21 | |||
22 | // Config is the configuration that comes from loading a collection | ||
23 | // of Terraform templates. | ||
24 | type Config struct { | ||
25 | // Dir is the path to the directory where this configuration was | ||
26 | // loaded from. If it is blank, this configuration wasn't loaded from | ||
27 | // any meaningful directory. | ||
28 | Dir string | ||
29 | |||
30 | Terraform *Terraform | ||
31 | Atlas *AtlasConfig | ||
32 | Modules []*Module | ||
33 | ProviderConfigs []*ProviderConfig | ||
34 | Resources []*Resource | ||
35 | Variables []*Variable | ||
36 | Outputs []*Output | ||
37 | |||
38 | // The fields below can be filled in by loaders for validation | ||
39 | // purposes. | ||
40 | unknownKeys []string | ||
41 | } | ||
42 | |||
43 | // AtlasConfig is the configuration for building in HashiCorp's Atlas. | ||
44 | type AtlasConfig struct { | ||
45 | Name string | ||
46 | Include []string | ||
47 | Exclude []string | ||
48 | } | ||
49 | |||
50 | // Module is a module used within a configuration. | ||
51 | // | ||
52 | // This does not represent a module itself, this represents a module | ||
53 | // call-site within an existing configuration. | ||
54 | type Module struct { | ||
55 | Name string | ||
56 | Source string | ||
57 | RawConfig *RawConfig | ||
58 | } | ||
59 | |||
60 | // ProviderConfig is the configuration for a resource provider. | ||
61 | // | ||
62 | // For example, Terraform needs to set the AWS access keys for the AWS | ||
63 | // resource provider. | ||
64 | type ProviderConfig struct { | ||
65 | Name string | ||
66 | Alias string | ||
67 | RawConfig *RawConfig | ||
68 | } | ||
69 | |||
70 | // A resource represents a single Terraform resource in the configuration. | ||
71 | // A Terraform resource is something that supports some or all of the | ||
72 | // usual "create, read, update, delete" operations, depending on | ||
73 | // the given Mode. | ||
74 | type Resource struct { | ||
75 | Mode ResourceMode // which operations the resource supports | ||
76 | Name string | ||
77 | Type string | ||
78 | RawCount *RawConfig | ||
79 | RawConfig *RawConfig | ||
80 | Provisioners []*Provisioner | ||
81 | Provider string | ||
82 | DependsOn []string | ||
83 | Lifecycle ResourceLifecycle | ||
84 | } | ||
85 | |||
86 | // Copy returns a copy of this Resource. Helpful for avoiding shared | ||
87 | // config pointers across multiple pieces of the graph that need to do | ||
88 | // interpolation. | ||
89 | func (r *Resource) Copy() *Resource { | ||
90 | n := &Resource{ | ||
91 | Mode: r.Mode, | ||
92 | Name: r.Name, | ||
93 | Type: r.Type, | ||
94 | RawCount: r.RawCount.Copy(), | ||
95 | RawConfig: r.RawConfig.Copy(), | ||
96 | Provisioners: make([]*Provisioner, 0, len(r.Provisioners)), | ||
97 | Provider: r.Provider, | ||
98 | DependsOn: make([]string, len(r.DependsOn)), | ||
99 | Lifecycle: *r.Lifecycle.Copy(), | ||
100 | } | ||
101 | for _, p := range r.Provisioners { | ||
102 | n.Provisioners = append(n.Provisioners, p.Copy()) | ||
103 | } | ||
104 | copy(n.DependsOn, r.DependsOn) | ||
105 | return n | ||
106 | } | ||
107 | |||
108 | // ResourceLifecycle is used to store the lifecycle tuning parameters | ||
109 | // to allow customized behavior | ||
110 | type ResourceLifecycle struct { | ||
111 | CreateBeforeDestroy bool `mapstructure:"create_before_destroy"` | ||
112 | PreventDestroy bool `mapstructure:"prevent_destroy"` | ||
113 | IgnoreChanges []string `mapstructure:"ignore_changes"` | ||
114 | } | ||
115 | |||
116 | // Copy returns a copy of this ResourceLifecycle | ||
117 | func (r *ResourceLifecycle) Copy() *ResourceLifecycle { | ||
118 | n := &ResourceLifecycle{ | ||
119 | CreateBeforeDestroy: r.CreateBeforeDestroy, | ||
120 | PreventDestroy: r.PreventDestroy, | ||
121 | IgnoreChanges: make([]string, len(r.IgnoreChanges)), | ||
122 | } | ||
123 | copy(n.IgnoreChanges, r.IgnoreChanges) | ||
124 | return n | ||
125 | } | ||
126 | |||
127 | // Provisioner is a configured provisioner step on a resource. | ||
128 | type Provisioner struct { | ||
129 | Type string | ||
130 | RawConfig *RawConfig | ||
131 | ConnInfo *RawConfig | ||
132 | |||
133 | When ProvisionerWhen | ||
134 | OnFailure ProvisionerOnFailure | ||
135 | } | ||
136 | |||
137 | // Copy returns a copy of this Provisioner | ||
138 | func (p *Provisioner) Copy() *Provisioner { | ||
139 | return &Provisioner{ | ||
140 | Type: p.Type, | ||
141 | RawConfig: p.RawConfig.Copy(), | ||
142 | ConnInfo: p.ConnInfo.Copy(), | ||
143 | When: p.When, | ||
144 | OnFailure: p.OnFailure, | ||
145 | } | ||
146 | } | ||
147 | |||
148 | // Variable is a variable defined within the configuration. | ||
149 | type Variable struct { | ||
150 | Name string | ||
151 | DeclaredType string `mapstructure:"type"` | ||
152 | Default interface{} | ||
153 | Description string | ||
154 | } | ||
155 | |||
156 | // Output is an output defined within the configuration. An output is | ||
157 | // resulting data that is highlighted by Terraform when finished. An | ||
158 | // output marked Sensitive will be output in a masked form following | ||
159 | // application, but will still be available in state. | ||
160 | type Output struct { | ||
161 | Name string | ||
162 | DependsOn []string | ||
163 | Description string | ||
164 | Sensitive bool | ||
165 | RawConfig *RawConfig | ||
166 | } | ||
167 | |||
168 | // VariableType is the type of value a variable is holding, and returned | ||
169 | // by the Type() function on variables. | ||
170 | type VariableType byte | ||
171 | |||
172 | const ( | ||
173 | VariableTypeUnknown VariableType = iota | ||
174 | VariableTypeString | ||
175 | VariableTypeList | ||
176 | VariableTypeMap | ||
177 | ) | ||
178 | |||
179 | func (v VariableType) Printable() string { | ||
180 | switch v { | ||
181 | case VariableTypeString: | ||
182 | return "string" | ||
183 | case VariableTypeMap: | ||
184 | return "map" | ||
185 | case VariableTypeList: | ||
186 | return "list" | ||
187 | default: | ||
188 | return "unknown" | ||
189 | } | ||
190 | } | ||
191 | |||
192 | // ProviderConfigName returns the name of the provider configuration in | ||
193 | // the given mapping that maps to the proper provider configuration | ||
194 | // for this resource. | ||
195 | func ProviderConfigName(t string, pcs []*ProviderConfig) string { | ||
196 | lk := "" | ||
197 | for _, v := range pcs { | ||
198 | k := v.Name | ||
199 | if strings.HasPrefix(t, k) && len(k) > len(lk) { | ||
200 | lk = k | ||
201 | } | ||
202 | } | ||
203 | |||
204 | return lk | ||
205 | } | ||
206 | |||
207 | // A unique identifier for this module. | ||
208 | func (r *Module) Id() string { | ||
209 | return fmt.Sprintf("%s", r.Name) | ||
210 | } | ||
211 | |||
212 | // Count returns the count of this resource. | ||
213 | func (r *Resource) Count() (int, error) { | ||
214 | raw := r.RawCount.Value() | ||
215 | count, ok := r.RawCount.Value().(string) | ||
216 | if !ok { | ||
217 | return 0, fmt.Errorf( | ||
218 | "expected count to be a string or int, got %T", raw) | ||
219 | } | ||
220 | |||
221 | v, err := strconv.ParseInt(count, 0, 0) | ||
222 | if err != nil { | ||
223 | return 0, err | ||
224 | } | ||
225 | |||
226 | return int(v), nil | ||
227 | } | ||
228 | |||
229 | // A unique identifier for this resource. | ||
230 | func (r *Resource) Id() string { | ||
231 | switch r.Mode { | ||
232 | case ManagedResourceMode: | ||
233 | return fmt.Sprintf("%s.%s", r.Type, r.Name) | ||
234 | case DataResourceMode: | ||
235 | return fmt.Sprintf("data.%s.%s", r.Type, r.Name) | ||
236 | default: | ||
237 | panic(fmt.Errorf("unknown resource mode %s", r.Mode)) | ||
238 | } | ||
239 | } | ||
240 | |||
241 | // Validate does some basic semantic checking of the configuration. | ||
242 | func (c *Config) Validate() error { | ||
243 | if c == nil { | ||
244 | return nil | ||
245 | } | ||
246 | |||
247 | var errs []error | ||
248 | |||
249 | for _, k := range c.unknownKeys { | ||
250 | errs = append(errs, fmt.Errorf( | ||
251 | "Unknown root level key: %s", k)) | ||
252 | } | ||
253 | |||
254 | // Validate the Terraform config | ||
255 | if tf := c.Terraform; tf != nil { | ||
256 | errs = append(errs, c.Terraform.Validate()...) | ||
257 | } | ||
258 | |||
259 | vars := c.InterpolatedVariables() | ||
260 | varMap := make(map[string]*Variable) | ||
261 | for _, v := range c.Variables { | ||
262 | if _, ok := varMap[v.Name]; ok { | ||
263 | errs = append(errs, fmt.Errorf( | ||
264 | "Variable '%s': duplicate found. Variable names must be unique.", | ||
265 | v.Name)) | ||
266 | } | ||
267 | |||
268 | varMap[v.Name] = v | ||
269 | } | ||
270 | |||
271 | for k, _ := range varMap { | ||
272 | if !NameRegexp.MatchString(k) { | ||
273 | errs = append(errs, fmt.Errorf( | ||
274 | "variable %q: variable name must match regular expresion %s", | ||
275 | k, NameRegexp)) | ||
276 | } | ||
277 | } | ||
278 | |||
279 | for _, v := range c.Variables { | ||
280 | if v.Type() == VariableTypeUnknown { | ||
281 | errs = append(errs, fmt.Errorf( | ||
282 | "Variable '%s': must be a string or a map", | ||
283 | v.Name)) | ||
284 | continue | ||
285 | } | ||
286 | |||
287 | interp := false | ||
288 | fn := func(n ast.Node) (interface{}, error) { | ||
289 | // LiteralNode is a literal string (outside of a ${ ... } sequence). | ||
290 | // interpolationWalker skips most of these. but in particular it | ||
291 | // visits those that have escaped sequences (like $${foo}) as a | ||
292 | // signal that *some* processing is required on this string. For | ||
293 | // our purposes here though, this is fine and not an interpolation. | ||
294 | if _, ok := n.(*ast.LiteralNode); !ok { | ||
295 | interp = true | ||
296 | } | ||
297 | return "", nil | ||
298 | } | ||
299 | |||
300 | w := &interpolationWalker{F: fn} | ||
301 | if v.Default != nil { | ||
302 | if err := reflectwalk.Walk(v.Default, w); err == nil { | ||
303 | if interp { | ||
304 | errs = append(errs, fmt.Errorf( | ||
305 | "Variable '%s': cannot contain interpolations", | ||
306 | v.Name)) | ||
307 | } | ||
308 | } | ||
309 | } | ||
310 | } | ||
311 | |||
312 | // Check for references to user variables that do not actually | ||
313 | // exist and record those errors. | ||
314 | for source, vs := range vars { | ||
315 | for _, v := range vs { | ||
316 | uv, ok := v.(*UserVariable) | ||
317 | if !ok { | ||
318 | continue | ||
319 | } | ||
320 | |||
321 | if _, ok := varMap[uv.Name]; !ok { | ||
322 | errs = append(errs, fmt.Errorf( | ||
323 | "%s: unknown variable referenced: '%s'. define it with 'variable' blocks", | ||
324 | source, | ||
325 | uv.Name)) | ||
326 | } | ||
327 | } | ||
328 | } | ||
329 | |||
330 | // Check that all count variables are valid. | ||
331 | for source, vs := range vars { | ||
332 | for _, rawV := range vs { | ||
333 | switch v := rawV.(type) { | ||
334 | case *CountVariable: | ||
335 | if v.Type == CountValueInvalid { | ||
336 | errs = append(errs, fmt.Errorf( | ||
337 | "%s: invalid count variable: %s", | ||
338 | source, | ||
339 | v.FullKey())) | ||
340 | } | ||
341 | case *PathVariable: | ||
342 | if v.Type == PathValueInvalid { | ||
343 | errs = append(errs, fmt.Errorf( | ||
344 | "%s: invalid path variable: %s", | ||
345 | source, | ||
346 | v.FullKey())) | ||
347 | } | ||
348 | } | ||
349 | } | ||
350 | } | ||
351 | |||
352 | // Check that providers aren't declared multiple times. | ||
353 | providerSet := make(map[string]struct{}) | ||
354 | for _, p := range c.ProviderConfigs { | ||
355 | name := p.FullName() | ||
356 | if _, ok := providerSet[name]; ok { | ||
357 | errs = append(errs, fmt.Errorf( | ||
358 | "provider.%s: declared multiple times, you can only declare a provider once", | ||
359 | name)) | ||
360 | continue | ||
361 | } | ||
362 | |||
363 | providerSet[name] = struct{}{} | ||
364 | } | ||
365 | |||
366 | // Check that all references to modules are valid | ||
367 | modules := make(map[string]*Module) | ||
368 | dupped := make(map[string]struct{}) | ||
369 | for _, m := range c.Modules { | ||
370 | // Check for duplicates | ||
371 | if _, ok := modules[m.Id()]; ok { | ||
372 | if _, ok := dupped[m.Id()]; !ok { | ||
373 | dupped[m.Id()] = struct{}{} | ||
374 | |||
375 | errs = append(errs, fmt.Errorf( | ||
376 | "%s: module repeated multiple times", | ||
377 | m.Id())) | ||
378 | } | ||
379 | |||
380 | // Already seen this module, just skip it | ||
381 | continue | ||
382 | } | ||
383 | |||
384 | modules[m.Id()] = m | ||
385 | |||
386 | // Check that the source has no interpolations | ||
387 | rc, err := NewRawConfig(map[string]interface{}{ | ||
388 | "root": m.Source, | ||
389 | }) | ||
390 | if err != nil { | ||
391 | errs = append(errs, fmt.Errorf( | ||
392 | "%s: module source error: %s", | ||
393 | m.Id(), err)) | ||
394 | } else if len(rc.Interpolations) > 0 { | ||
395 | errs = append(errs, fmt.Errorf( | ||
396 | "%s: module source cannot contain interpolations", | ||
397 | m.Id())) | ||
398 | } | ||
399 | |||
400 | // Check that the name matches our regexp | ||
401 | if !NameRegexp.Match([]byte(m.Name)) { | ||
402 | errs = append(errs, fmt.Errorf( | ||
403 | "%s: module name can only contain letters, numbers, "+ | ||
404 | "dashes, and underscores", | ||
405 | m.Id())) | ||
406 | } | ||
407 | |||
408 | // Check that the configuration can all be strings, lists or maps | ||
409 | raw := make(map[string]interface{}) | ||
410 | for k, v := range m.RawConfig.Raw { | ||
411 | var strVal string | ||
412 | if err := hilmapstructure.WeakDecode(v, &strVal); err == nil { | ||
413 | raw[k] = strVal | ||
414 | continue | ||
415 | } | ||
416 | |||
417 | var mapVal map[string]interface{} | ||
418 | if err := hilmapstructure.WeakDecode(v, &mapVal); err == nil { | ||
419 | raw[k] = mapVal | ||
420 | continue | ||
421 | } | ||
422 | |||
423 | var sliceVal []interface{} | ||
424 | if err := hilmapstructure.WeakDecode(v, &sliceVal); err == nil { | ||
425 | raw[k] = sliceVal | ||
426 | continue | ||
427 | } | ||
428 | |||
429 | errs = append(errs, fmt.Errorf( | ||
430 | "%s: variable %s must be a string, list or map value", | ||
431 | m.Id(), k)) | ||
432 | } | ||
433 | |||
434 | // Check for invalid count variables | ||
435 | for _, v := range m.RawConfig.Variables { | ||
436 | switch v.(type) { | ||
437 | case *CountVariable: | ||
438 | errs = append(errs, fmt.Errorf( | ||
439 | "%s: count variables are only valid within resources", m.Name)) | ||
440 | case *SelfVariable: | ||
441 | errs = append(errs, fmt.Errorf( | ||
442 | "%s: self variables are only valid within resources", m.Name)) | ||
443 | } | ||
444 | } | ||
445 | |||
446 | // Update the raw configuration to only contain the string values | ||
447 | m.RawConfig, err = NewRawConfig(raw) | ||
448 | if err != nil { | ||
449 | errs = append(errs, fmt.Errorf( | ||
450 | "%s: can't initialize configuration: %s", | ||
451 | m.Id(), err)) | ||
452 | } | ||
453 | } | ||
454 | dupped = nil | ||
455 | |||
456 | // Check that all variables for modules reference modules that | ||
457 | // exist. | ||
458 | for source, vs := range vars { | ||
459 | for _, v := range vs { | ||
460 | mv, ok := v.(*ModuleVariable) | ||
461 | if !ok { | ||
462 | continue | ||
463 | } | ||
464 | |||
465 | if _, ok := modules[mv.Name]; !ok { | ||
466 | errs = append(errs, fmt.Errorf( | ||
467 | "%s: unknown module referenced: %s", | ||
468 | source, | ||
469 | mv.Name)) | ||
470 | } | ||
471 | } | ||
472 | } | ||
473 | |||
474 | // Check that all references to resources are valid | ||
475 | resources := make(map[string]*Resource) | ||
476 | dupped = make(map[string]struct{}) | ||
477 | for _, r := range c.Resources { | ||
478 | if _, ok := resources[r.Id()]; ok { | ||
479 | if _, ok := dupped[r.Id()]; !ok { | ||
480 | dupped[r.Id()] = struct{}{} | ||
481 | |||
482 | errs = append(errs, fmt.Errorf( | ||
483 | "%s: resource repeated multiple times", | ||
484 | r.Id())) | ||
485 | } | ||
486 | } | ||
487 | |||
488 | resources[r.Id()] = r | ||
489 | } | ||
490 | dupped = nil | ||
491 | |||
492 | // Validate resources | ||
493 | for n, r := range resources { | ||
494 | // Verify count variables | ||
495 | for _, v := range r.RawCount.Variables { | ||
496 | switch v.(type) { | ||
497 | case *CountVariable: | ||
498 | errs = append(errs, fmt.Errorf( | ||
499 | "%s: resource count can't reference count variable: %s", | ||
500 | n, | ||
501 | v.FullKey())) | ||
502 | case *SimpleVariable: | ||
503 | errs = append(errs, fmt.Errorf( | ||
504 | "%s: resource count can't reference variable: %s", | ||
505 | n, | ||
506 | v.FullKey())) | ||
507 | |||
508 | // Good | ||
509 | case *ModuleVariable: | ||
510 | case *ResourceVariable: | ||
511 | case *TerraformVariable: | ||
512 | case *UserVariable: | ||
513 | |||
514 | default: | ||
515 | errs = append(errs, fmt.Errorf( | ||
516 | "Internal error. Unknown type in count var in %s: %T", | ||
517 | n, v)) | ||
518 | } | ||
519 | } | ||
520 | |||
521 | // Interpolate with a fixed number to verify that its a number. | ||
522 | r.RawCount.interpolate(func(root ast.Node) (interface{}, error) { | ||
523 | // Execute the node but transform the AST so that it returns | ||
524 | // a fixed value of "5" for all interpolations. | ||
525 | result, err := hil.Eval( | ||
526 | hil.FixedValueTransform( | ||
527 | root, &ast.LiteralNode{Value: "5", Typex: ast.TypeString}), | ||
528 | nil) | ||
529 | if err != nil { | ||
530 | return "", err | ||
531 | } | ||
532 | |||
533 | return result.Value, nil | ||
534 | }) | ||
535 | _, err := strconv.ParseInt(r.RawCount.Value().(string), 0, 0) | ||
536 | if err != nil { | ||
537 | errs = append(errs, fmt.Errorf( | ||
538 | "%s: resource count must be an integer", | ||
539 | n)) | ||
540 | } | ||
541 | r.RawCount.init() | ||
542 | |||
543 | // Validate DependsOn | ||
544 | errs = append(errs, c.validateDependsOn(n, r.DependsOn, resources, modules)...) | ||
545 | |||
546 | // Verify provisioners | ||
547 | for _, p := range r.Provisioners { | ||
548 | // This validation checks that there are now splat variables | ||
549 | // referencing ourself. This currently is not allowed. | ||
550 | |||
551 | for _, v := range p.ConnInfo.Variables { | ||
552 | rv, ok := v.(*ResourceVariable) | ||
553 | if !ok { | ||
554 | continue | ||
555 | } | ||
556 | |||
557 | if rv.Multi && rv.Index == -1 && rv.Type == r.Type && rv.Name == r.Name { | ||
558 | errs = append(errs, fmt.Errorf( | ||
559 | "%s: connection info cannot contain splat variable "+ | ||
560 | "referencing itself", n)) | ||
561 | break | ||
562 | } | ||
563 | } | ||
564 | |||
565 | for _, v := range p.RawConfig.Variables { | ||
566 | rv, ok := v.(*ResourceVariable) | ||
567 | if !ok { | ||
568 | continue | ||
569 | } | ||
570 | |||
571 | if rv.Multi && rv.Index == -1 && rv.Type == r.Type && rv.Name == r.Name { | ||
572 | errs = append(errs, fmt.Errorf( | ||
573 | "%s: connection info cannot contain splat variable "+ | ||
574 | "referencing itself", n)) | ||
575 | break | ||
576 | } | ||
577 | } | ||
578 | |||
579 | // Check for invalid when/onFailure values, though this should be | ||
580 | // picked up by the loader we check here just in case. | ||
581 | if p.When == ProvisionerWhenInvalid { | ||
582 | errs = append(errs, fmt.Errorf( | ||
583 | "%s: provisioner 'when' value is invalid", n)) | ||
584 | } | ||
585 | if p.OnFailure == ProvisionerOnFailureInvalid { | ||
586 | errs = append(errs, fmt.Errorf( | ||
587 | "%s: provisioner 'on_failure' value is invalid", n)) | ||
588 | } | ||
589 | } | ||
590 | |||
591 | // Verify ignore_changes contains valid entries | ||
592 | for _, v := range r.Lifecycle.IgnoreChanges { | ||
593 | if strings.Contains(v, "*") && v != "*" { | ||
594 | errs = append(errs, fmt.Errorf( | ||
595 | "%s: ignore_changes does not support using a partial string "+ | ||
596 | "together with a wildcard: %s", n, v)) | ||
597 | } | ||
598 | } | ||
599 | |||
600 | // Verify ignore_changes has no interpolations | ||
601 | rc, err := NewRawConfig(map[string]interface{}{ | ||
602 | "root": r.Lifecycle.IgnoreChanges, | ||
603 | }) | ||
604 | if err != nil { | ||
605 | errs = append(errs, fmt.Errorf( | ||
606 | "%s: lifecycle ignore_changes error: %s", | ||
607 | n, err)) | ||
608 | } else if len(rc.Interpolations) > 0 { | ||
609 | errs = append(errs, fmt.Errorf( | ||
610 | "%s: lifecycle ignore_changes cannot contain interpolations", | ||
611 | n)) | ||
612 | } | ||
613 | |||
614 | // If it is a data source then it can't have provisioners | ||
615 | if r.Mode == DataResourceMode { | ||
616 | if _, ok := r.RawConfig.Raw["provisioner"]; ok { | ||
617 | errs = append(errs, fmt.Errorf( | ||
618 | "%s: data sources cannot have provisioners", | ||
619 | n)) | ||
620 | } | ||
621 | } | ||
622 | } | ||
623 | |||
624 | for source, vs := range vars { | ||
625 | for _, v := range vs { | ||
626 | rv, ok := v.(*ResourceVariable) | ||
627 | if !ok { | ||
628 | continue | ||
629 | } | ||
630 | |||
631 | id := rv.ResourceId() | ||
632 | if _, ok := resources[id]; !ok { | ||
633 | errs = append(errs, fmt.Errorf( | ||
634 | "%s: unknown resource '%s' referenced in variable %s", | ||
635 | source, | ||
636 | id, | ||
637 | rv.FullKey())) | ||
638 | continue | ||
639 | } | ||
640 | } | ||
641 | } | ||
642 | |||
643 | // Check that all outputs are valid | ||
644 | { | ||
645 | found := make(map[string]struct{}) | ||
646 | for _, o := range c.Outputs { | ||
647 | // Verify the output is new | ||
648 | if _, ok := found[o.Name]; ok { | ||
649 | errs = append(errs, fmt.Errorf( | ||
650 | "%s: duplicate output. output names must be unique.", | ||
651 | o.Name)) | ||
652 | continue | ||
653 | } | ||
654 | found[o.Name] = struct{}{} | ||
655 | |||
656 | var invalidKeys []string | ||
657 | valueKeyFound := false | ||
658 | for k := range o.RawConfig.Raw { | ||
659 | if k == "value" { | ||
660 | valueKeyFound = true | ||
661 | continue | ||
662 | } | ||
663 | if k == "sensitive" { | ||
664 | if sensitive, ok := o.RawConfig.config[k].(bool); ok { | ||
665 | if sensitive { | ||
666 | o.Sensitive = true | ||
667 | } | ||
668 | continue | ||
669 | } | ||
670 | |||
671 | errs = append(errs, fmt.Errorf( | ||
672 | "%s: value for 'sensitive' must be boolean", | ||
673 | o.Name)) | ||
674 | continue | ||
675 | } | ||
676 | if k == "description" { | ||
677 | if desc, ok := o.RawConfig.config[k].(string); ok { | ||
678 | o.Description = desc | ||
679 | continue | ||
680 | } | ||
681 | |||
682 | errs = append(errs, fmt.Errorf( | ||
683 | "%s: value for 'description' must be string", | ||
684 | o.Name)) | ||
685 | continue | ||
686 | } | ||
687 | invalidKeys = append(invalidKeys, k) | ||
688 | } | ||
689 | if len(invalidKeys) > 0 { | ||
690 | errs = append(errs, fmt.Errorf( | ||
691 | "%s: output has invalid keys: %s", | ||
692 | o.Name, strings.Join(invalidKeys, ", "))) | ||
693 | } | ||
694 | if !valueKeyFound { | ||
695 | errs = append(errs, fmt.Errorf( | ||
696 | "%s: output is missing required 'value' key", o.Name)) | ||
697 | } | ||
698 | |||
699 | for _, v := range o.RawConfig.Variables { | ||
700 | if _, ok := v.(*CountVariable); ok { | ||
701 | errs = append(errs, fmt.Errorf( | ||
702 | "%s: count variables are only valid within resources", o.Name)) | ||
703 | } | ||
704 | } | ||
705 | } | ||
706 | } | ||
707 | |||
708 | // Check that all variables are in the proper context | ||
709 | for source, rc := range c.rawConfigs() { | ||
710 | walker := &interpolationWalker{ | ||
711 | ContextF: c.validateVarContextFn(source, &errs), | ||
712 | } | ||
713 | if err := reflectwalk.Walk(rc.Raw, walker); err != nil { | ||
714 | errs = append(errs, fmt.Errorf( | ||
715 | "%s: error reading config: %s", source, err)) | ||
716 | } | ||
717 | } | ||
718 | |||
719 | // Validate the self variable | ||
720 | for source, rc := range c.rawConfigs() { | ||
721 | // Ignore provisioners. This is a pretty brittle way to do this, | ||
722 | // but better than also repeating all the resources. | ||
723 | if strings.Contains(source, "provision") { | ||
724 | continue | ||
725 | } | ||
726 | |||
727 | for _, v := range rc.Variables { | ||
728 | if _, ok := v.(*SelfVariable); ok { | ||
729 | errs = append(errs, fmt.Errorf( | ||
730 | "%s: cannot contain self-reference %s", source, v.FullKey())) | ||
731 | } | ||
732 | } | ||
733 | } | ||
734 | |||
735 | if len(errs) > 0 { | ||
736 | return &multierror.Error{Errors: errs} | ||
737 | } | ||
738 | |||
739 | return nil | ||
740 | } | ||
741 | |||
742 | // InterpolatedVariables is a helper that returns a mapping of all the interpolated | ||
743 | // variables within the configuration. This is used to verify references | ||
744 | // are valid in the Validate step. | ||
745 | func (c *Config) InterpolatedVariables() map[string][]InterpolatedVariable { | ||
746 | result := make(map[string][]InterpolatedVariable) | ||
747 | for source, rc := range c.rawConfigs() { | ||
748 | for _, v := range rc.Variables { | ||
749 | result[source] = append(result[source], v) | ||
750 | } | ||
751 | } | ||
752 | return result | ||
753 | } | ||
754 | |||
755 | // rawConfigs returns all of the RawConfigs that are available keyed by | ||
756 | // a human-friendly source. | ||
757 | func (c *Config) rawConfigs() map[string]*RawConfig { | ||
758 | result := make(map[string]*RawConfig) | ||
759 | for _, m := range c.Modules { | ||
760 | source := fmt.Sprintf("module '%s'", m.Name) | ||
761 | result[source] = m.RawConfig | ||
762 | } | ||
763 | |||
764 | for _, pc := range c.ProviderConfigs { | ||
765 | source := fmt.Sprintf("provider config '%s'", pc.Name) | ||
766 | result[source] = pc.RawConfig | ||
767 | } | ||
768 | |||
769 | for _, rc := range c.Resources { | ||
770 | source := fmt.Sprintf("resource '%s'", rc.Id()) | ||
771 | result[source+" count"] = rc.RawCount | ||
772 | result[source+" config"] = rc.RawConfig | ||
773 | |||
774 | for i, p := range rc.Provisioners { | ||
775 | subsource := fmt.Sprintf( | ||
776 | "%s provisioner %s (#%d)", | ||
777 | source, p.Type, i+1) | ||
778 | result[subsource] = p.RawConfig | ||
779 | } | ||
780 | } | ||
781 | |||
782 | for _, o := range c.Outputs { | ||
783 | source := fmt.Sprintf("output '%s'", o.Name) | ||
784 | result[source] = o.RawConfig | ||
785 | } | ||
786 | |||
787 | return result | ||
788 | } | ||
789 | |||
790 | func (c *Config) validateVarContextFn( | ||
791 | source string, errs *[]error) interpolationWalkerContextFunc { | ||
792 | return func(loc reflectwalk.Location, node ast.Node) { | ||
793 | // If we're in a slice element, then its fine, since you can do | ||
794 | // anything in there. | ||
795 | if loc == reflectwalk.SliceElem { | ||
796 | return | ||
797 | } | ||
798 | |||
799 | // Otherwise, let's check if there is a splat resource variable | ||
800 | // at the top level in here. We do this by doing a transform that | ||
801 | // replaces everything with a noop node unless its a variable | ||
802 | // access or concat. This should turn the AST into a flat tree | ||
803 | // of Concat(Noop, ...). If there are any variables left that are | ||
804 | // multi-access, then its still broken. | ||
805 | node = node.Accept(func(n ast.Node) ast.Node { | ||
806 | // If it is a concat or variable access, we allow it. | ||
807 | switch n.(type) { | ||
808 | case *ast.Output: | ||
809 | return n | ||
810 | case *ast.VariableAccess: | ||
811 | return n | ||
812 | } | ||
813 | |||
814 | // Otherwise, noop | ||
815 | return &noopNode{} | ||
816 | }) | ||
817 | |||
818 | vars, err := DetectVariables(node) | ||
819 | if err != nil { | ||
820 | // Ignore it since this will be caught during parse. This | ||
821 | // actually probably should never happen by the time this | ||
822 | // is called, but its okay. | ||
823 | return | ||
824 | } | ||
825 | |||
826 | for _, v := range vars { | ||
827 | rv, ok := v.(*ResourceVariable) | ||
828 | if !ok { | ||
829 | return | ||
830 | } | ||
831 | |||
832 | if rv.Multi && rv.Index == -1 { | ||
833 | *errs = append(*errs, fmt.Errorf( | ||
834 | "%s: use of the splat ('*') operator must be wrapped in a list declaration", | ||
835 | source)) | ||
836 | } | ||
837 | } | ||
838 | } | ||
839 | } | ||
840 | |||
841 | func (c *Config) validateDependsOn( | ||
842 | n string, | ||
843 | v []string, | ||
844 | resources map[string]*Resource, | ||
845 | modules map[string]*Module) []error { | ||
846 | // Verify depends on points to resources that all exist | ||
847 | var errs []error | ||
848 | for _, d := range v { | ||
849 | // Check if we contain interpolations | ||
850 | rc, err := NewRawConfig(map[string]interface{}{ | ||
851 | "value": d, | ||
852 | }) | ||
853 | if err == nil && len(rc.Variables) > 0 { | ||
854 | errs = append(errs, fmt.Errorf( | ||
855 | "%s: depends on value cannot contain interpolations: %s", | ||
856 | n, d)) | ||
857 | continue | ||
858 | } | ||
859 | |||
860 | // If it is a module, verify it is a module | ||
861 | if strings.HasPrefix(d, "module.") { | ||
862 | name := d[len("module."):] | ||
863 | if _, ok := modules[name]; !ok { | ||
864 | errs = append(errs, fmt.Errorf( | ||
865 | "%s: resource depends on non-existent module '%s'", | ||
866 | n, name)) | ||
867 | } | ||
868 | |||
869 | continue | ||
870 | } | ||
871 | |||
872 | // Check resources | ||
873 | if _, ok := resources[d]; !ok { | ||
874 | errs = append(errs, fmt.Errorf( | ||
875 | "%s: resource depends on non-existent resource '%s'", | ||
876 | n, d)) | ||
877 | } | ||
878 | } | ||
879 | |||
880 | return errs | ||
881 | } | ||
882 | |||
883 | func (m *Module) mergerName() string { | ||
884 | return m.Id() | ||
885 | } | ||
886 | |||
887 | func (m *Module) mergerMerge(other merger) merger { | ||
888 | m2 := other.(*Module) | ||
889 | |||
890 | result := *m | ||
891 | result.Name = m2.Name | ||
892 | result.RawConfig = result.RawConfig.merge(m2.RawConfig) | ||
893 | |||
894 | if m2.Source != "" { | ||
895 | result.Source = m2.Source | ||
896 | } | ||
897 | |||
898 | return &result | ||
899 | } | ||
900 | |||
901 | func (o *Output) mergerName() string { | ||
902 | return o.Name | ||
903 | } | ||
904 | |||
905 | func (o *Output) mergerMerge(m merger) merger { | ||
906 | o2 := m.(*Output) | ||
907 | |||
908 | result := *o | ||
909 | result.Name = o2.Name | ||
910 | result.Description = o2.Description | ||
911 | result.RawConfig = result.RawConfig.merge(o2.RawConfig) | ||
912 | result.Sensitive = o2.Sensitive | ||
913 | result.DependsOn = o2.DependsOn | ||
914 | |||
915 | return &result | ||
916 | } | ||
917 | |||
918 | func (c *ProviderConfig) GoString() string { | ||
919 | return fmt.Sprintf("*%#v", *c) | ||
920 | } | ||
921 | |||
922 | func (c *ProviderConfig) FullName() string { | ||
923 | if c.Alias == "" { | ||
924 | return c.Name | ||
925 | } | ||
926 | |||
927 | return fmt.Sprintf("%s.%s", c.Name, c.Alias) | ||
928 | } | ||
929 | |||
930 | func (c *ProviderConfig) mergerName() string { | ||
931 | return c.Name | ||
932 | } | ||
933 | |||
934 | func (c *ProviderConfig) mergerMerge(m merger) merger { | ||
935 | c2 := m.(*ProviderConfig) | ||
936 | |||
937 | result := *c | ||
938 | result.Name = c2.Name | ||
939 | result.RawConfig = result.RawConfig.merge(c2.RawConfig) | ||
940 | |||
941 | if c2.Alias != "" { | ||
942 | result.Alias = c2.Alias | ||
943 | } | ||
944 | |||
945 | return &result | ||
946 | } | ||
947 | |||
948 | func (r *Resource) mergerName() string { | ||
949 | return r.Id() | ||
950 | } | ||
951 | |||
952 | func (r *Resource) mergerMerge(m merger) merger { | ||
953 | r2 := m.(*Resource) | ||
954 | |||
955 | result := *r | ||
956 | result.Mode = r2.Mode | ||
957 | result.Name = r2.Name | ||
958 | result.Type = r2.Type | ||
959 | result.RawConfig = result.RawConfig.merge(r2.RawConfig) | ||
960 | |||
961 | if r2.RawCount.Value() != "1" { | ||
962 | result.RawCount = r2.RawCount | ||
963 | } | ||
964 | |||
965 | if len(r2.Provisioners) > 0 { | ||
966 | result.Provisioners = r2.Provisioners | ||
967 | } | ||
968 | |||
969 | return &result | ||
970 | } | ||
971 | |||
972 | // Merge merges two variables to create a new third variable. | ||
973 | func (v *Variable) Merge(v2 *Variable) *Variable { | ||
974 | // Shallow copy the variable | ||
975 | result := *v | ||
976 | |||
977 | // The names should be the same, but the second name always wins. | ||
978 | result.Name = v2.Name | ||
979 | |||
980 | if v2.DeclaredType != "" { | ||
981 | result.DeclaredType = v2.DeclaredType | ||
982 | } | ||
983 | if v2.Default != nil { | ||
984 | result.Default = v2.Default | ||
985 | } | ||
986 | if v2.Description != "" { | ||
987 | result.Description = v2.Description | ||
988 | } | ||
989 | |||
990 | return &result | ||
991 | } | ||
992 | |||
993 | var typeStringMap = map[string]VariableType{ | ||
994 | "string": VariableTypeString, | ||
995 | "map": VariableTypeMap, | ||
996 | "list": VariableTypeList, | ||
997 | } | ||
998 | |||
999 | // Type returns the type of variable this is. | ||
1000 | func (v *Variable) Type() VariableType { | ||
1001 | if v.DeclaredType != "" { | ||
1002 | declaredType, ok := typeStringMap[v.DeclaredType] | ||
1003 | if !ok { | ||
1004 | return VariableTypeUnknown | ||
1005 | } | ||
1006 | |||
1007 | return declaredType | ||
1008 | } | ||
1009 | |||
1010 | return v.inferTypeFromDefault() | ||
1011 | } | ||
1012 | |||
1013 | // ValidateTypeAndDefault ensures that default variable value is compatible | ||
1014 | // with the declared type (if one exists), and that the type is one which is | ||
1015 | // known to Terraform | ||
1016 | func (v *Variable) ValidateTypeAndDefault() error { | ||
1017 | // If an explicit type is declared, ensure it is valid | ||
1018 | if v.DeclaredType != "" { | ||
1019 | if _, ok := typeStringMap[v.DeclaredType]; !ok { | ||
1020 | validTypes := []string{} | ||
1021 | for k := range typeStringMap { | ||
1022 | validTypes = append(validTypes, k) | ||
1023 | } | ||
1024 | return fmt.Errorf( | ||
1025 | "Variable '%s' type must be one of [%s] - '%s' is not a valid type", | ||
1026 | v.Name, | ||
1027 | strings.Join(validTypes, ", "), | ||
1028 | v.DeclaredType, | ||
1029 | ) | ||
1030 | } | ||
1031 | } | ||
1032 | |||
1033 | if v.DeclaredType == "" || v.Default == nil { | ||
1034 | return nil | ||
1035 | } | ||
1036 | |||
1037 | if v.inferTypeFromDefault() != v.Type() { | ||
1038 | return fmt.Errorf("'%s' has a default value which is not of type '%s' (got '%s')", | ||
1039 | v.Name, v.DeclaredType, v.inferTypeFromDefault().Printable()) | ||
1040 | } | ||
1041 | |||
1042 | return nil | ||
1043 | } | ||
1044 | |||
1045 | func (v *Variable) mergerName() string { | ||
1046 | return v.Name | ||
1047 | } | ||
1048 | |||
1049 | func (v *Variable) mergerMerge(m merger) merger { | ||
1050 | return v.Merge(m.(*Variable)) | ||
1051 | } | ||
1052 | |||
1053 | // Required tests whether a variable is required or not. | ||
1054 | func (v *Variable) Required() bool { | ||
1055 | return v.Default == nil | ||
1056 | } | ||
1057 | |||
1058 | // inferTypeFromDefault contains the logic for the old method of inferring | ||
1059 | // variable types - we can also use this for validating that the declared | ||
1060 | // type matches the type of the default value | ||
1061 | func (v *Variable) inferTypeFromDefault() VariableType { | ||
1062 | if v.Default == nil { | ||
1063 | return VariableTypeString | ||
1064 | } | ||
1065 | |||
1066 | var s string | ||
1067 | if err := hilmapstructure.WeakDecode(v.Default, &s); err == nil { | ||
1068 | v.Default = s | ||
1069 | return VariableTypeString | ||
1070 | } | ||
1071 | |||
1072 | var m map[string]interface{} | ||
1073 | if err := hilmapstructure.WeakDecode(v.Default, &m); err == nil { | ||
1074 | v.Default = m | ||
1075 | return VariableTypeMap | ||
1076 | } | ||
1077 | |||
1078 | var l []interface{} | ||
1079 | if err := hilmapstructure.WeakDecode(v.Default, &l); err == nil { | ||
1080 | v.Default = l | ||
1081 | return VariableTypeList | ||
1082 | } | ||
1083 | |||
1084 | return VariableTypeUnknown | ||
1085 | } | ||
1086 | |||
1087 | func (m ResourceMode) Taintable() bool { | ||
1088 | switch m { | ||
1089 | case ManagedResourceMode: | ||
1090 | return true | ||
1091 | case DataResourceMode: | ||
1092 | return false | ||
1093 | default: | ||
1094 | panic(fmt.Errorf("unsupported ResourceMode value %s", m)) | ||
1095 | } | ||
1096 | } | ||