Repository navigation
Expand file tree
/
Copy pathloader.go
More file actions
134 lines (109 loc) · 3.33 KB
/
Copy pathloader.go
File metadata and controls
134 lines (109 loc) · 3.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package config
import (
"fmt"
"github.com/mitchellh/go-libucl"
)
// Put the parse flags we use for libucl in a constant so we can get
// equally behaving parsing everywhere.
const libuclParseFlags = libucl.ParserKeyLowercase
// Load loads the Terraform configuration from a given file.
func Load(path string) (*Config, error) {
var rawConfig struct {
Variable map[string]Variable
Object *libucl.Object `libucl:",object"`
}
// Parse the libucl file into the raw format
if err := parseFile(path, &rawConfig); err != nil {
return nil, err
}
// Make sure we close the raw object
defer rawConfig.Object.Close()
// Start building up the actual configuration. We first
// copy the fields that can be directly assigned.
config := new(Config)
config.Variables = rawConfig.Variable
// Build the resources
resources := rawConfig.Object.Get("resource")
if resources != nil {
defer resources.Close()
var err error
config.Resources, err = loadResourcesLibucl(resources)
if err != nil {
return nil, err
}
}
return config, nil
}
func loadResourcesLibucl(o *libucl.Object) ([]Resource, error) {
var allTypes []*libucl.Object
// Libucl object iteration is really nasty. Below is likely to make
// no sense to anyone approaching this code. Luckily, it is very heavily
// tested. If working on a bug fix or feature, we recommend writing a
// test first then doing whatever you want to the code below. If you
// break it, the tests will catch it. Likewise, if you change this,
// MAKE SURE you write a test for your change, because its fairly impossible
// to reason about this mess.
//
// Functionally, what the code does below is get the libucl.Objects
// for all the TYPES, such as "aws_security_group".
iter := o.Iterate(false)
for o1 := iter.Next(); o1 != nil; o1 = iter.Next() {
// Iterate the inner to get the list of types
iter2 := o1.Iterate(true)
for o2 := iter2.Next(); o2 != nil; o2 = iter2.Next() {
// Iterate all of this type to get _all_ the types
iter3 := o2.Iterate(false)
for o3 := iter3.Next(); o3 != nil; o3 = iter3.Next() {
allTypes = append(allTypes, o3)
}
o2.Close()
iter3.Close()
}
o1.Close()
iter2.Close()
}
iter.Close()
// Where all the results will go
var result []Resource
// Now go over all the types and their children in order to get
// all of the actual resources.
for _, t := range allTypes {
// Release the resources for this raw type since we don't need it.
// Note that this makes it unsafe now to use allTypes again.
defer t.Close()
iter := t.Iterate(true)
defer iter.Close()
for r := iter.Next(); r != nil; r = iter.Next() {
defer r.Close()
var config map[string]interface{}
if err := r.Decode(&config); err != nil {
return nil, fmt.Errorf(
"Error reading config for %s[%s]: %s",
t.Key(),
r.Key(),
err)
}
result = append(result, Resource{
Name: r.Key(),
Type: t.Key(),
Config: config,
})
}
}
return result, nil
}
// Helper for parsing a single libucl-formatted file into
// the given structure.
func parseFile(path string, result interface{}) error {
parser := libucl.NewParser(libuclParseFlags)
defer parser.Close()
if err := parser.AddFile(path); err != nil {
return err
}
root := parser.Object()
defer root.Close()
if err := root.Decode(result); err != nil {
return err
}
return nil
}