]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/config/loader_hcl.go
vendor: github.com/hashicorp/terraform/...@v0.10.0
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / config / loader_hcl.go
1 package config
2
3 import (
4 "fmt"
5 "io/ioutil"
6
7 "github.com/hashicorp/go-multierror"
8 "github.com/hashicorp/hcl"
9 "github.com/hashicorp/hcl/hcl/ast"
10 "github.com/mitchellh/mapstructure"
11 )
12
13 // hclConfigurable is an implementation of configurable that knows
14 // how to turn HCL configuration into a *Config object.
15 type hclConfigurable struct {
16 File string
17 Root *ast.File
18 }
19
20 var ReservedResourceFields = []string{
21 "connection",
22 "count",
23 "depends_on",
24 "lifecycle",
25 "provider",
26 "provisioner",
27 }
28
29 var ReservedProviderFields = []string{
30 "alias",
31 "version",
32 }
33
34 func (t *hclConfigurable) Config() (*Config, error) {
35 validKeys := map[string]struct{}{
36 "atlas": struct{}{},
37 "data": struct{}{},
38 "module": struct{}{},
39 "output": struct{}{},
40 "provider": struct{}{},
41 "resource": struct{}{},
42 "terraform": struct{}{},
43 "variable": struct{}{},
44 }
45
46 // Top-level item should be the object list
47 list, ok := t.Root.Node.(*ast.ObjectList)
48 if !ok {
49 return nil, fmt.Errorf("error parsing: file doesn't contain a root object")
50 }
51
52 // Start building up the actual configuration.
53 config := new(Config)
54
55 // Terraform config
56 if o := list.Filter("terraform"); len(o.Items) > 0 {
57 var err error
58 config.Terraform, err = loadTerraformHcl(o)
59 if err != nil {
60 return nil, err
61 }
62 }
63
64 // Build the variables
65 if vars := list.Filter("variable"); len(vars.Items) > 0 {
66 var err error
67 config.Variables, err = loadVariablesHcl(vars)
68 if err != nil {
69 return nil, err
70 }
71 }
72
73 // Get Atlas configuration
74 if atlas := list.Filter("atlas"); len(atlas.Items) > 0 {
75 var err error
76 config.Atlas, err = loadAtlasHcl(atlas)
77 if err != nil {
78 return nil, err
79 }
80 }
81
82 // Build the modules
83 if modules := list.Filter("module"); len(modules.Items) > 0 {
84 var err error
85 config.Modules, err = loadModulesHcl(modules)
86 if err != nil {
87 return nil, err
88 }
89 }
90
91 // Build the provider configs
92 if providers := list.Filter("provider"); len(providers.Items) > 0 {
93 var err error
94 config.ProviderConfigs, err = loadProvidersHcl(providers)
95 if err != nil {
96 return nil, err
97 }
98 }
99
100 // Build the resources
101 {
102 var err error
103 managedResourceConfigs := list.Filter("resource")
104 dataResourceConfigs := list.Filter("data")
105
106 config.Resources = make(
107 []*Resource, 0,
108 len(managedResourceConfigs.Items)+len(dataResourceConfigs.Items),
109 )
110
111 managedResources, err := loadManagedResourcesHcl(managedResourceConfigs)
112 if err != nil {
113 return nil, err
114 }
115 dataResources, err := loadDataResourcesHcl(dataResourceConfigs)
116 if err != nil {
117 return nil, err
118 }
119
120 config.Resources = append(config.Resources, dataResources...)
121 config.Resources = append(config.Resources, managedResources...)
122 }
123
124 // Build the outputs
125 if outputs := list.Filter("output"); len(outputs.Items) > 0 {
126 var err error
127 config.Outputs, err = loadOutputsHcl(outputs)
128 if err != nil {
129 return nil, err
130 }
131 }
132
133 // Check for invalid keys
134 for _, item := range list.Items {
135 if len(item.Keys) == 0 {
136 // Not sure how this would happen, but let's avoid a panic
137 continue
138 }
139
140 k := item.Keys[0].Token.Value().(string)
141 if _, ok := validKeys[k]; ok {
142 continue
143 }
144
145 config.unknownKeys = append(config.unknownKeys, k)
146 }
147
148 return config, nil
149 }
150
151 // loadFileHcl is a fileLoaderFunc that knows how to read HCL
152 // files and turn them into hclConfigurables.
153 func loadFileHcl(root string) (configurable, []string, error) {
154 // Read the HCL file and prepare for parsing
155 d, err := ioutil.ReadFile(root)
156 if err != nil {
157 return nil, nil, fmt.Errorf(
158 "Error reading %s: %s", root, err)
159 }
160
161 // Parse it
162 hclRoot, err := hcl.Parse(string(d))
163 if err != nil {
164 return nil, nil, fmt.Errorf(
165 "Error parsing %s: %s", root, err)
166 }
167
168 // Start building the result
169 result := &hclConfigurable{
170 File: root,
171 Root: hclRoot,
172 }
173
174 // Dive in, find the imports. This is disabled for now since
175 // imports were removed prior to Terraform 0.1. The code is
176 // remaining here commented for historical purposes.
177 /*
178 imports := obj.Get("import")
179 if imports == nil {
180 result.Object.Ref()
181 return result, nil, nil
182 }
183
184 if imports.Type() != libucl.ObjectTypeString {
185 imports.Close()
186
187 return nil, nil, fmt.Errorf(
188 "Error in %s: all 'import' declarations should be in the format\n"+
189 "`import \"foo\"` (Got type %s)",
190 root,
191 imports.Type())
192 }
193
194 // Gather all the import paths
195 importPaths := make([]string, 0, imports.Len())
196 iter := imports.Iterate(false)
197 for imp := iter.Next(); imp != nil; imp = iter.Next() {
198 path := imp.ToString()
199 if !filepath.IsAbs(path) {
200 // Relative paths are relative to the Terraform file itself
201 dir := filepath.Dir(root)
202 path = filepath.Join(dir, path)
203 }
204
205 importPaths = append(importPaths, path)
206 imp.Close()
207 }
208 iter.Close()
209 imports.Close()
210
211 result.Object.Ref()
212 */
213
214 return result, nil, nil
215 }
216
217 // Given a handle to a HCL object, this transforms it into the Terraform config
218 func loadTerraformHcl(list *ast.ObjectList) (*Terraform, error) {
219 if len(list.Items) > 1 {
220 return nil, fmt.Errorf("only one 'terraform' block allowed per module")
221 }
222
223 // Get our one item
224 item := list.Items[0]
225
226 // This block should have an empty top level ObjectItem. If there are keys
227 // here, it's likely because we have a flattened JSON object, and we can
228 // lift this into a nested ObjectList to decode properly.
229 if len(item.Keys) > 0 {
230 item = &ast.ObjectItem{
231 Val: &ast.ObjectType{
232 List: &ast.ObjectList{
233 Items: []*ast.ObjectItem{item},
234 },
235 },
236 }
237 }
238
239 // We need the item value as an ObjectList
240 var listVal *ast.ObjectList
241 if ot, ok := item.Val.(*ast.ObjectType); ok {
242 listVal = ot.List
243 } else {
244 return nil, fmt.Errorf("terraform block: should be an object")
245 }
246
247 // NOTE: We purposely don't validate unknown HCL keys here so that
248 // we can potentially read _future_ Terraform version config (to
249 // still be able to validate the required version).
250 //
251 // We should still keep track of unknown keys to validate later, but
252 // HCL doesn't currently support that.
253
254 var config Terraform
255 if err := hcl.DecodeObject(&config, item.Val); err != nil {
256 return nil, fmt.Errorf(
257 "Error reading terraform config: %s",
258 err)
259 }
260
261 // If we have provisioners, then parse those out
262 if os := listVal.Filter("backend"); len(os.Items) > 0 {
263 var err error
264 config.Backend, err = loadTerraformBackendHcl(os)
265 if err != nil {
266 return nil, fmt.Errorf(
267 "Error reading backend config for terraform block: %s",
268 err)
269 }
270 }
271
272 return &config, nil
273 }
274
275 // Loads the Backend configuration from an object list.
276 func loadTerraformBackendHcl(list *ast.ObjectList) (*Backend, error) {
277 if len(list.Items) > 1 {
278 return nil, fmt.Errorf("only one 'backend' block allowed")
279 }
280
281 // Get our one item
282 item := list.Items[0]
283
284 // Verify the keys
285 if len(item.Keys) != 1 {
286 return nil, fmt.Errorf(
287 "position %s: 'backend' must be followed by exactly one string: a type",
288 item.Pos())
289 }
290
291 typ := item.Keys[0].Token.Value().(string)
292
293 // Decode the raw config
294 var config map[string]interface{}
295 if err := hcl.DecodeObject(&config, item.Val); err != nil {
296 return nil, fmt.Errorf(
297 "Error reading backend config: %s",
298 err)
299 }
300
301 rawConfig, err := NewRawConfig(config)
302 if err != nil {
303 return nil, fmt.Errorf(
304 "Error reading backend config: %s",
305 err)
306 }
307
308 b := &Backend{
309 Type: typ,
310 RawConfig: rawConfig,
311 }
312 b.Hash = b.Rehash()
313
314 return b, nil
315 }
316
317 // Given a handle to a HCL object, this transforms it into the Atlas
318 // configuration.
319 func loadAtlasHcl(list *ast.ObjectList) (*AtlasConfig, error) {
320 if len(list.Items) > 1 {
321 return nil, fmt.Errorf("only one 'atlas' block allowed")
322 }
323
324 // Get our one item
325 item := list.Items[0]
326
327 var config AtlasConfig
328 if err := hcl.DecodeObject(&config, item.Val); err != nil {
329 return nil, fmt.Errorf(
330 "Error reading atlas config: %s",
331 err)
332 }
333
334 return &config, nil
335 }
336
337 // Given a handle to a HCL object, this recurses into the structure
338 // and pulls out a list of modules.
339 //
340 // The resulting modules may not be unique, but each module
341 // represents exactly one module definition in the HCL configuration.
342 // We leave it up to another pass to merge them together.
343 func loadModulesHcl(list *ast.ObjectList) ([]*Module, error) {
344 if err := assertAllBlocksHaveNames("module", list); err != nil {
345 return nil, err
346 }
347
348 list = list.Children()
349 if len(list.Items) == 0 {
350 return nil, nil
351 }
352
353 // Where all the results will go
354 var result []*Module
355
356 // Now go over all the types and their children in order to get
357 // all of the actual resources.
358 for _, item := range list.Items {
359 k := item.Keys[0].Token.Value().(string)
360
361 var listVal *ast.ObjectList
362 if ot, ok := item.Val.(*ast.ObjectType); ok {
363 listVal = ot.List
364 } else {
365 return nil, fmt.Errorf("module '%s': should be an object", k)
366 }
367
368 var config map[string]interface{}
369 if err := hcl.DecodeObject(&config, item.Val); err != nil {
370 return nil, fmt.Errorf(
371 "Error reading config for %s: %s",
372 k,
373 err)
374 }
375
376 // Remove the fields we handle specially
377 delete(config, "source")
378
379 rawConfig, err := NewRawConfig(config)
380 if err != nil {
381 return nil, fmt.Errorf(
382 "Error reading config for %s: %s",
383 k,
384 err)
385 }
386
387 // If we have a count, then figure it out
388 var source string
389 if o := listVal.Filter("source"); len(o.Items) > 0 {
390 err = hcl.DecodeObject(&source, o.Items[0].Val)
391 if err != nil {
392 return nil, fmt.Errorf(
393 "Error parsing source for %s: %s",
394 k,
395 err)
396 }
397 }
398
399 result = append(result, &Module{
400 Name: k,
401 Source: source,
402 RawConfig: rawConfig,
403 })
404 }
405
406 return result, nil
407 }
408
409 // LoadOutputsHcl recurses into the given HCL object and turns
410 // it into a mapping of outputs.
411 func loadOutputsHcl(list *ast.ObjectList) ([]*Output, error) {
412 if err := assertAllBlocksHaveNames("output", list); err != nil {
413 return nil, err
414 }
415
416 list = list.Children()
417
418 // Go through each object and turn it into an actual result.
419 result := make([]*Output, 0, len(list.Items))
420 for _, item := range list.Items {
421 n := item.Keys[0].Token.Value().(string)
422
423 var listVal *ast.ObjectList
424 if ot, ok := item.Val.(*ast.ObjectType); ok {
425 listVal = ot.List
426 } else {
427 return nil, fmt.Errorf("output '%s': should be an object", n)
428 }
429
430 var config map[string]interface{}
431 if err := hcl.DecodeObject(&config, item.Val); err != nil {
432 return nil, err
433 }
434
435 // Delete special keys
436 delete(config, "depends_on")
437
438 rawConfig, err := NewRawConfig(config)
439 if err != nil {
440 return nil, fmt.Errorf(
441 "Error reading config for output %s: %s",
442 n,
443 err)
444 }
445
446 // If we have depends fields, then add those in
447 var dependsOn []string
448 if o := listVal.Filter("depends_on"); len(o.Items) > 0 {
449 err := hcl.DecodeObject(&dependsOn, o.Items[0].Val)
450 if err != nil {
451 return nil, fmt.Errorf(
452 "Error reading depends_on for output %q: %s",
453 n,
454 err)
455 }
456 }
457
458 result = append(result, &Output{
459 Name: n,
460 RawConfig: rawConfig,
461 DependsOn: dependsOn,
462 })
463 }
464
465 return result, nil
466 }
467
468 // LoadVariablesHcl recurses into the given HCL object and turns
469 // it into a list of variables.
470 func loadVariablesHcl(list *ast.ObjectList) ([]*Variable, error) {
471 if err := assertAllBlocksHaveNames("variable", list); err != nil {
472 return nil, err
473 }
474
475 list = list.Children()
476
477 // hclVariable is the structure each variable is decoded into
478 type hclVariable struct {
479 DeclaredType string `hcl:"type"`
480 Default interface{}
481 Description string
482 Fields []string `hcl:",decodedFields"`
483 }
484
485 // Go through each object and turn it into an actual result.
486 result := make([]*Variable, 0, len(list.Items))
487 for _, item := range list.Items {
488 // Clean up items from JSON
489 unwrapHCLObjectKeysFromJSON(item, 1)
490
491 // Verify the keys
492 if len(item.Keys) != 1 {
493 return nil, fmt.Errorf(
494 "position %s: 'variable' must be followed by exactly one strings: a name",
495 item.Pos())
496 }
497
498 n := item.Keys[0].Token.Value().(string)
499 if !NameRegexp.MatchString(n) {
500 return nil, fmt.Errorf(
501 "position %s: 'variable' name must match regular expression: %s",
502 item.Pos(), NameRegexp)
503 }
504
505 // Check for invalid keys
506 valid := []string{"type", "default", "description"}
507 if err := checkHCLKeys(item.Val, valid); err != nil {
508 return nil, multierror.Prefix(err, fmt.Sprintf(
509 "variable[%s]:", n))
510 }
511
512 // Decode into hclVariable to get typed values
513 var hclVar hclVariable
514 if err := hcl.DecodeObject(&hclVar, item.Val); err != nil {
515 return nil, err
516 }
517
518 // Defaults turn into a slice of map[string]interface{} and
519 // we need to make sure to convert that down into the
520 // proper type for Config.
521 if ms, ok := hclVar.Default.([]map[string]interface{}); ok {
522 def := make(map[string]interface{})
523 for _, m := range ms {
524 for k, v := range m {
525 def[k] = v
526 }
527 }
528
529 hclVar.Default = def
530 }
531
532 // Build the new variable and do some basic validation
533 newVar := &Variable{
534 Name: n,
535 DeclaredType: hclVar.DeclaredType,
536 Default: hclVar.Default,
537 Description: hclVar.Description,
538 }
539 if err := newVar.ValidateTypeAndDefault(); err != nil {
540 return nil, err
541 }
542
543 result = append(result, newVar)
544 }
545
546 return result, nil
547 }
548
549 // LoadProvidersHcl recurses into the given HCL object and turns
550 // it into a mapping of provider configs.
551 func loadProvidersHcl(list *ast.ObjectList) ([]*ProviderConfig, error) {
552 if err := assertAllBlocksHaveNames("provider", list); err != nil {
553 return nil, err
554 }
555
556 list = list.Children()
557 if len(list.Items) == 0 {
558 return nil, nil
559 }
560
561 // Go through each object and turn it into an actual result.
562 result := make([]*ProviderConfig, 0, len(list.Items))
563 for _, item := range list.Items {
564 n := item.Keys[0].Token.Value().(string)
565
566 var listVal *ast.ObjectList
567 if ot, ok := item.Val.(*ast.ObjectType); ok {
568 listVal = ot.List
569 } else {
570 return nil, fmt.Errorf("module '%s': should be an object", n)
571 }
572
573 var config map[string]interface{}
574 if err := hcl.DecodeObject(&config, item.Val); err != nil {
575 return nil, err
576 }
577
578 delete(config, "alias")
579 delete(config, "version")
580
581 rawConfig, err := NewRawConfig(config)
582 if err != nil {
583 return nil, fmt.Errorf(
584 "Error reading config for provider config %s: %s",
585 n,
586 err)
587 }
588
589 // If we have an alias field, then add those in
590 var alias string
591 if a := listVal.Filter("alias"); len(a.Items) > 0 {
592 err := hcl.DecodeObject(&alias, a.Items[0].Val)
593 if err != nil {
594 return nil, fmt.Errorf(
595 "Error reading alias for provider[%s]: %s",
596 n,
597 err)
598 }
599 }
600
601 // If we have a version field then extract it
602 var version string
603 if a := listVal.Filter("version"); len(a.Items) > 0 {
604 err := hcl.DecodeObject(&version, a.Items[0].Val)
605 if err != nil {
606 return nil, fmt.Errorf(
607 "Error reading version for provider[%s]: %s",
608 n,
609 err)
610 }
611 }
612
613 result = append(result, &ProviderConfig{
614 Name: n,
615 Alias: alias,
616 Version: version,
617 RawConfig: rawConfig,
618 })
619 }
620
621 return result, nil
622 }
623
624 // Given a handle to a HCL object, this recurses into the structure
625 // and pulls out a list of data sources.
626 //
627 // The resulting data sources may not be unique, but each one
628 // represents exactly one data definition in the HCL configuration.
629 // We leave it up to another pass to merge them together.
630 func loadDataResourcesHcl(list *ast.ObjectList) ([]*Resource, error) {
631 if err := assertAllBlocksHaveNames("data", list); err != nil {
632 return nil, err
633 }
634
635 list = list.Children()
636 if len(list.Items) == 0 {
637 return nil, nil
638 }
639
640 // Where all the results will go
641 var result []*Resource
642
643 // Now go over all the types and their children in order to get
644 // all of the actual resources.
645 for _, item := range list.Items {
646 if len(item.Keys) != 2 {
647 return nil, fmt.Errorf(
648 "position %s: 'data' must be followed by exactly two strings: a type and a name",
649 item.Pos())
650 }
651
652 t := item.Keys[0].Token.Value().(string)
653 k := item.Keys[1].Token.Value().(string)
654
655 var listVal *ast.ObjectList
656 if ot, ok := item.Val.(*ast.ObjectType); ok {
657 listVal = ot.List
658 } else {
659 return nil, fmt.Errorf("data sources %s[%s]: should be an object", t, k)
660 }
661
662 var config map[string]interface{}
663 if err := hcl.DecodeObject(&config, item.Val); err != nil {
664 return nil, fmt.Errorf(
665 "Error reading config for %s[%s]: %s",
666 t,
667 k,
668 err)
669 }
670
671 // Remove the fields we handle specially
672 delete(config, "depends_on")
673 delete(config, "provider")
674 delete(config, "count")
675
676 rawConfig, err := NewRawConfig(config)
677 if err != nil {
678 return nil, fmt.Errorf(
679 "Error reading config for %s[%s]: %s",
680 t,
681 k,
682 err)
683 }
684
685 // If we have a count, then figure it out
686 var count string = "1"
687 if o := listVal.Filter("count"); len(o.Items) > 0 {
688 err = hcl.DecodeObject(&count, o.Items[0].Val)
689 if err != nil {
690 return nil, fmt.Errorf(
691 "Error parsing count for %s[%s]: %s",
692 t,
693 k,
694 err)
695 }
696 }
697 countConfig, err := NewRawConfig(map[string]interface{}{
698 "count": count,
699 })
700 if err != nil {
701 return nil, err
702 }
703 countConfig.Key = "count"
704
705 // If we have depends fields, then add those in
706 var dependsOn []string
707 if o := listVal.Filter("depends_on"); len(o.Items) > 0 {
708 err := hcl.DecodeObject(&dependsOn, o.Items[0].Val)
709 if err != nil {
710 return nil, fmt.Errorf(
711 "Error reading depends_on for %s[%s]: %s",
712 t,
713 k,
714 err)
715 }
716 }
717
718 // If we have a provider, then parse it out
719 var provider string
720 if o := listVal.Filter("provider"); len(o.Items) > 0 {
721 err := hcl.DecodeObject(&provider, o.Items[0].Val)
722 if err != nil {
723 return nil, fmt.Errorf(
724 "Error reading provider for %s[%s]: %s",
725 t,
726 k,
727 err)
728 }
729 }
730
731 result = append(result, &Resource{
732 Mode: DataResourceMode,
733 Name: k,
734 Type: t,
735 RawCount: countConfig,
736 RawConfig: rawConfig,
737 Provider: provider,
738 Provisioners: []*Provisioner{},
739 DependsOn: dependsOn,
740 Lifecycle: ResourceLifecycle{},
741 })
742 }
743
744 return result, nil
745 }
746
747 // Given a handle to a HCL object, this recurses into the structure
748 // and pulls out a list of managed resources.
749 //
750 // The resulting resources may not be unique, but each resource
751 // represents exactly one "resource" block in the HCL configuration.
752 // We leave it up to another pass to merge them together.
753 func loadManagedResourcesHcl(list *ast.ObjectList) ([]*Resource, error) {
754 list = list.Children()
755 if len(list.Items) == 0 {
756 return nil, nil
757 }
758
759 // Where all the results will go
760 var result []*Resource
761
762 // Now go over all the types and their children in order to get
763 // all of the actual resources.
764 for _, item := range list.Items {
765 // GH-4385: We detect a pure provisioner resource and give the user
766 // an error about how to do it cleanly.
767 if len(item.Keys) == 4 && item.Keys[2].Token.Value().(string) == "provisioner" {
768 return nil, fmt.Errorf(
769 "position %s: provisioners in a resource should be wrapped in a list\n\n"+
770 "Example: \"provisioner\": [ { \"local-exec\": ... } ]",
771 item.Pos())
772 }
773
774 // Fix up JSON input
775 unwrapHCLObjectKeysFromJSON(item, 2)
776
777 if len(item.Keys) != 2 {
778 return nil, fmt.Errorf(
779 "position %s: resource must be followed by exactly two strings, a type and a name",
780 item.Pos())
781 }
782
783 t := item.Keys[0].Token.Value().(string)
784 k := item.Keys[1].Token.Value().(string)
785
786 var listVal *ast.ObjectList
787 if ot, ok := item.Val.(*ast.ObjectType); ok {
788 listVal = ot.List
789 } else {
790 return nil, fmt.Errorf("resources %s[%s]: should be an object", t, k)
791 }
792
793 var config map[string]interface{}
794 if err := hcl.DecodeObject(&config, item.Val); err != nil {
795 return nil, fmt.Errorf(
796 "Error reading config for %s[%s]: %s",
797 t,
798 k,
799 err)
800 }
801
802 // Remove the fields we handle specially
803 delete(config, "connection")
804 delete(config, "count")
805 delete(config, "depends_on")
806 delete(config, "provisioner")
807 delete(config, "provider")
808 delete(config, "lifecycle")
809
810 rawConfig, err := NewRawConfig(config)
811 if err != nil {
812 return nil, fmt.Errorf(
813 "Error reading config for %s[%s]: %s",
814 t,
815 k,
816 err)
817 }
818
819 // If we have a count, then figure it out
820 var count string = "1"
821 if o := listVal.Filter("count"); len(o.Items) > 0 {
822 err = hcl.DecodeObject(&count, o.Items[0].Val)
823 if err != nil {
824 return nil, fmt.Errorf(
825 "Error parsing count for %s[%s]: %s",
826 t,
827 k,
828 err)
829 }
830 }
831 countConfig, err := NewRawConfig(map[string]interface{}{
832 "count": count,
833 })
834 if err != nil {
835 return nil, err
836 }
837 countConfig.Key = "count"
838
839 // If we have depends fields, then add those in
840 var dependsOn []string
841 if o := listVal.Filter("depends_on"); len(o.Items) > 0 {
842 err := hcl.DecodeObject(&dependsOn, o.Items[0].Val)
843 if err != nil {
844 return nil, fmt.Errorf(
845 "Error reading depends_on for %s[%s]: %s",
846 t,
847 k,
848 err)
849 }
850 }
851
852 // If we have connection info, then parse those out
853 var connInfo map[string]interface{}
854 if o := listVal.Filter("connection"); len(o.Items) > 0 {
855 err := hcl.DecodeObject(&connInfo, o.Items[0].Val)
856 if err != nil {
857 return nil, fmt.Errorf(
858 "Error reading connection info for %s[%s]: %s",
859 t,
860 k,
861 err)
862 }
863 }
864
865 // If we have provisioners, then parse those out
866 var provisioners []*Provisioner
867 if os := listVal.Filter("provisioner"); len(os.Items) > 0 {
868 var err error
869 provisioners, err = loadProvisionersHcl(os, connInfo)
870 if err != nil {
871 return nil, fmt.Errorf(
872 "Error reading provisioners for %s[%s]: %s",
873 t,
874 k,
875 err)
876 }
877 }
878
879 // If we have a provider, then parse it out
880 var provider string
881 if o := listVal.Filter("provider"); len(o.Items) > 0 {
882 err := hcl.DecodeObject(&provider, o.Items[0].Val)
883 if err != nil {
884 return nil, fmt.Errorf(
885 "Error reading provider for %s[%s]: %s",
886 t,
887 k,
888 err)
889 }
890 }
891
892 // Check if the resource should be re-created before
893 // destroying the existing instance
894 var lifecycle ResourceLifecycle
895 if o := listVal.Filter("lifecycle"); len(o.Items) > 0 {
896 if len(o.Items) > 1 {
897 return nil, fmt.Errorf(
898 "%s[%s]: Multiple lifecycle blocks found, expected one",
899 t, k)
900 }
901
902 // Check for invalid keys
903 valid := []string{"create_before_destroy", "ignore_changes", "prevent_destroy"}
904 if err := checkHCLKeys(o.Items[0].Val, valid); err != nil {
905 return nil, multierror.Prefix(err, fmt.Sprintf(
906 "%s[%s]:", t, k))
907 }
908
909 var raw map[string]interface{}
910 if err = hcl.DecodeObject(&raw, o.Items[0].Val); err != nil {
911 return nil, fmt.Errorf(
912 "Error parsing lifecycle for %s[%s]: %s",
913 t,
914 k,
915 err)
916 }
917
918 if err := mapstructure.WeakDecode(raw, &lifecycle); err != nil {
919 return nil, fmt.Errorf(
920 "Error parsing lifecycle for %s[%s]: %s",
921 t,
922 k,
923 err)
924 }
925 }
926
927 result = append(result, &Resource{
928 Mode: ManagedResourceMode,
929 Name: k,
930 Type: t,
931 RawCount: countConfig,
932 RawConfig: rawConfig,
933 Provisioners: provisioners,
934 Provider: provider,
935 DependsOn: dependsOn,
936 Lifecycle: lifecycle,
937 })
938 }
939
940 return result, nil
941 }
942
943 func loadProvisionersHcl(list *ast.ObjectList, connInfo map[string]interface{}) ([]*Provisioner, error) {
944 if err := assertAllBlocksHaveNames("provisioner", list); err != nil {
945 return nil, err
946 }
947
948 list = list.Children()
949 if len(list.Items) == 0 {
950 return nil, nil
951 }
952
953 // Go through each object and turn it into an actual result.
954 result := make([]*Provisioner, 0, len(list.Items))
955 for _, item := range list.Items {
956 n := item.Keys[0].Token.Value().(string)
957
958 var listVal *ast.ObjectList
959 if ot, ok := item.Val.(*ast.ObjectType); ok {
960 listVal = ot.List
961 } else {
962 return nil, fmt.Errorf("provisioner '%s': should be an object", n)
963 }
964
965 var config map[string]interface{}
966 if err := hcl.DecodeObject(&config, item.Val); err != nil {
967 return nil, err
968 }
969
970 // Parse the "when" value
971 when := ProvisionerWhenCreate
972 if v, ok := config["when"]; ok {
973 switch v {
974 case "create":
975 when = ProvisionerWhenCreate
976 case "destroy":
977 when = ProvisionerWhenDestroy
978 default:
979 return nil, fmt.Errorf(
980 "position %s: 'provisioner' when must be 'create' or 'destroy'",
981 item.Pos())
982 }
983 }
984
985 // Parse the "on_failure" value
986 onFailure := ProvisionerOnFailureFail
987 if v, ok := config["on_failure"]; ok {
988 switch v {
989 case "continue":
990 onFailure = ProvisionerOnFailureContinue
991 case "fail":
992 onFailure = ProvisionerOnFailureFail
993 default:
994 return nil, fmt.Errorf(
995 "position %s: 'provisioner' on_failure must be 'continue' or 'fail'",
996 item.Pos())
997 }
998 }
999
1000 // Delete fields we special case
1001 delete(config, "connection")
1002 delete(config, "when")
1003 delete(config, "on_failure")
1004
1005 rawConfig, err := NewRawConfig(config)
1006 if err != nil {
1007 return nil, err
1008 }
1009
1010 // Check if we have a provisioner-level connection
1011 // block that overrides the resource-level
1012 var subConnInfo map[string]interface{}
1013 if o := listVal.Filter("connection"); len(o.Items) > 0 {
1014 err := hcl.DecodeObject(&subConnInfo, o.Items[0].Val)
1015 if err != nil {
1016 return nil, err
1017 }
1018 }
1019
1020 // Inherit from the resource connInfo any keys
1021 // that are not explicitly overriden.
1022 if connInfo != nil && subConnInfo != nil {
1023 for k, v := range connInfo {
1024 if _, ok := subConnInfo[k]; !ok {
1025 subConnInfo[k] = v
1026 }
1027 }
1028 } else if subConnInfo == nil {
1029 subConnInfo = connInfo
1030 }
1031
1032 // Parse the connInfo
1033 connRaw, err := NewRawConfig(subConnInfo)
1034 if err != nil {
1035 return nil, err
1036 }
1037
1038 result = append(result, &Provisioner{
1039 Type: n,
1040 RawConfig: rawConfig,
1041 ConnInfo: connRaw,
1042 When: when,
1043 OnFailure: onFailure,
1044 })
1045 }
1046
1047 return result, nil
1048 }
1049
1050 /*
1051 func hclObjectMap(os *hclobj.Object) map[string]ast.ListNode {
1052 objects := make(map[string][]*hclobj.Object)
1053
1054 for _, o := range os.Elem(false) {
1055 for _, elem := range o.Elem(true) {
1056 val, ok := objects[elem.Key]
1057 if !ok {
1058 val = make([]*hclobj.Object, 0, 1)
1059 }
1060
1061 val = append(val, elem)
1062 objects[elem.Key] = val
1063 }
1064 }
1065
1066 return objects
1067 }
1068 */
1069
1070 // assertAllBlocksHaveNames returns an error if any of the items in
1071 // the given object list are blocks without keys (like "module {}")
1072 // or simple assignments (like "module = 1"). It returns nil if
1073 // neither of these things are true.
1074 //
1075 // The given name is used in any generated error messages, and should
1076 // be the name of the block we're dealing with. The given list should
1077 // be the result of calling .Filter on an object list with that same
1078 // name.
1079 func assertAllBlocksHaveNames(name string, list *ast.ObjectList) error {
1080 if elem := list.Elem(); len(elem.Items) != 0 {
1081 switch et := elem.Items[0].Val.(type) {
1082 case *ast.ObjectType:
1083 pos := et.Lbrace
1084 return fmt.Errorf("%s: %q must be followed by a name", pos, name)
1085 default:
1086 pos := elem.Items[0].Val.Pos()
1087 return fmt.Errorf("%s: %q must be a configuration block", pos, name)
1088 }
1089 }
1090 return nil
1091 }
1092
1093 func checkHCLKeys(node ast.Node, valid []string) error {
1094 var list *ast.ObjectList
1095 switch n := node.(type) {
1096 case *ast.ObjectList:
1097 list = n
1098 case *ast.ObjectType:
1099 list = n.List
1100 default:
1101 return fmt.Errorf("cannot check HCL keys of type %T", n)
1102 }
1103
1104 validMap := make(map[string]struct{}, len(valid))
1105 for _, v := range valid {
1106 validMap[v] = struct{}{}
1107 }
1108
1109 var result error
1110 for _, item := range list.Items {
1111 key := item.Keys[0].Token.Value().(string)
1112 if _, ok := validMap[key]; !ok {
1113 result = multierror.Append(result, fmt.Errorf(
1114 "invalid key: %s", key))
1115 }
1116 }
1117
1118 return result
1119 }
1120
1121 // unwrapHCLObjectKeysFromJSON cleans up an edge case that can occur when
1122 // parsing JSON as input: if we're parsing JSON then directly nested
1123 // items will show up as additional "keys".
1124 //
1125 // For objects that expect a fixed number of keys, this breaks the
1126 // decoding process. This function unwraps the object into what it would've
1127 // looked like if it came directly from HCL by specifying the number of keys
1128 // you expect.
1129 //
1130 // Example:
1131 //
1132 // { "foo": { "baz": {} } }
1133 //
1134 // Will show up with Keys being: []string{"foo", "baz"}
1135 // when we really just want the first two. This function will fix this.
1136 func unwrapHCLObjectKeysFromJSON(item *ast.ObjectItem, depth int) {
1137 if len(item.Keys) > depth && item.Keys[0].Token.JSON {
1138 for len(item.Keys) > depth {
1139 // Pop off the last key
1140 n := len(item.Keys)
1141 key := item.Keys[n-1]
1142 item.Keys[n-1] = nil
1143 item.Keys = item.Keys[:n-1]
1144
1145 // Wrap our value in a list
1146 item.Val = &ast.ObjectType{
1147 List: &ast.ObjectList{
1148 Items: []*ast.ObjectItem{
1149 &ast.ObjectItem{
1150 Keys: []*ast.ObjectKey{key},
1151 Val: item.Val,
1152 },
1153 },
1154 },
1155 }
1156 }
1157 }
1158 }