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