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