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