diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform-config-inspect/tfconfig/load_legacy.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform-config-inspect/tfconfig/load_legacy.go | 325 |
1 files changed, 325 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform-config-inspect/tfconfig/load_legacy.go b/vendor/github.com/hashicorp/terraform-config-inspect/tfconfig/load_legacy.go new file mode 100644 index 0000000..86ffdf1 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-config-inspect/tfconfig/load_legacy.go | |||
@@ -0,0 +1,325 @@ | |||
1 | package tfconfig | ||
2 | |||
3 | import ( | ||
4 | "io/ioutil" | ||
5 | "strings" | ||
6 | |||
7 | legacyhcl "github.com/hashicorp/hcl" | ||
8 | legacyast "github.com/hashicorp/hcl/hcl/ast" | ||
9 | ) | ||
10 | |||
11 | func loadModuleLegacyHCL(dir string) (*Module, Diagnostics) { | ||
12 | // This implementation is intentionally more quick-and-dirty than the | ||
13 | // main loader. In particular, it doesn't bother to keep careful track | ||
14 | // of multiple error messages because we always fall back on returning | ||
15 | // the main parser's error message if our fallback parsing produces | ||
16 | // an error, and thus the errors here are not seen by the end-caller. | ||
17 | mod := newModule(dir) | ||
18 | |||
19 | primaryPaths, diags := dirFiles(dir) | ||
20 | if diags.HasErrors() { | ||
21 | return mod, diagnosticsHCL(diags) | ||
22 | } | ||
23 | |||
24 | for _, filename := range primaryPaths { | ||
25 | src, err := ioutil.ReadFile(filename) | ||
26 | if err != nil { | ||
27 | return mod, diagnosticsErrorf("Error reading %s: %s", filename, err) | ||
28 | } | ||
29 | |||
30 | hclRoot, err := legacyhcl.Parse(string(src)) | ||
31 | if err != nil { | ||
32 | return mod, diagnosticsErrorf("Error parsing %s: %s", filename, err) | ||
33 | } | ||
34 | |||
35 | list, ok := hclRoot.Node.(*legacyast.ObjectList) | ||
36 | if !ok { | ||
37 | return mod, diagnosticsErrorf("Error parsing %s: no root object", filename) | ||
38 | } | ||
39 | |||
40 | for _, item := range list.Filter("terraform").Items { | ||
41 | if len(item.Keys) > 0 { | ||
42 | item = &legacyast.ObjectItem{ | ||
43 | Val: &legacyast.ObjectType{ | ||
44 | List: &legacyast.ObjectList{ | ||
45 | Items: []*legacyast.ObjectItem{item}, | ||
46 | }, | ||
47 | }, | ||
48 | } | ||
49 | } | ||
50 | |||
51 | type TerraformBlock struct { | ||
52 | RequiredVersion string `hcl:"required_version"` | ||
53 | } | ||
54 | var block TerraformBlock | ||
55 | err = legacyhcl.DecodeObject(&block, item.Val) | ||
56 | if err != nil { | ||
57 | return nil, diagnosticsErrorf("terraform block: %s", err) | ||
58 | } | ||
59 | |||
60 | if block.RequiredVersion != "" { | ||
61 | mod.RequiredCore = append(mod.RequiredCore, block.RequiredVersion) | ||
62 | } | ||
63 | } | ||
64 | |||
65 | if vars := list.Filter("variable"); len(vars.Items) > 0 { | ||
66 | vars = vars.Children() | ||
67 | type VariableBlock struct { | ||
68 | Type string `hcl:"type"` | ||
69 | Default interface{} | ||
70 | Description string | ||
71 | Fields []string `hcl:",decodedFields"` | ||
72 | } | ||
73 | |||
74 | for _, item := range vars.Items { | ||
75 | unwrapLegacyHCLObjectKeysFromJSON(item, 1) | ||
76 | |||
77 | if len(item.Keys) != 1 { | ||
78 | return nil, diagnosticsErrorf("variable block at %s has no label", item.Pos()) | ||
79 | } | ||
80 | |||
81 | name := item.Keys[0].Token.Value().(string) | ||
82 | |||
83 | var block VariableBlock | ||
84 | err := legacyhcl.DecodeObject(&block, item.Val) | ||
85 | if err != nil { | ||
86 | return nil, diagnosticsErrorf("invalid variable block at %s: %s", item.Pos(), err) | ||
87 | } | ||
88 | |||
89 | // Clean up legacy HCL decoding ambiguity by unwrapping list of maps | ||
90 | if ms, ok := block.Default.([]map[string]interface{}); ok { | ||
91 | def := make(map[string]interface{}) | ||
92 | for _, m := range ms { | ||
93 | for k, v := range m { | ||
94 | def[k] = v | ||
95 | } | ||
96 | } | ||
97 | block.Default = def | ||
98 | } | ||
99 | |||
100 | v := &Variable{ | ||
101 | Name: name, | ||
102 | Type: block.Type, | ||
103 | Description: block.Description, | ||
104 | Default: block.Default, | ||
105 | Pos: sourcePosLegacyHCL(item.Pos(), filename), | ||
106 | } | ||
107 | if _, exists := mod.Variables[name]; exists { | ||
108 | return nil, diagnosticsErrorf("duplicate variable block for %q", name) | ||
109 | } | ||
110 | mod.Variables[name] = v | ||
111 | |||
112 | } | ||
113 | } | ||
114 | |||
115 | if outputs := list.Filter("output"); len(outputs.Items) > 0 { | ||
116 | outputs = outputs.Children() | ||
117 | type OutputBlock struct { | ||
118 | Description string | ||
119 | } | ||
120 | |||
121 | for _, item := range outputs.Items { | ||
122 | unwrapLegacyHCLObjectKeysFromJSON(item, 1) | ||
123 | |||
124 | if len(item.Keys) != 1 { | ||
125 | return nil, diagnosticsErrorf("output block at %s has no label", item.Pos()) | ||
126 | } | ||
127 | |||
128 | name := item.Keys[0].Token.Value().(string) | ||
129 | |||
130 | var block OutputBlock | ||
131 | err := legacyhcl.DecodeObject(&block, item.Val) | ||
132 | if err != nil { | ||
133 | return nil, diagnosticsErrorf("invalid output block at %s: %s", item.Pos(), err) | ||
134 | } | ||
135 | |||
136 | o := &Output{ | ||
137 | Name: name, | ||
138 | Description: block.Description, | ||
139 | Pos: sourcePosLegacyHCL(item.Pos(), filename), | ||
140 | } | ||
141 | if _, exists := mod.Outputs[name]; exists { | ||
142 | return nil, diagnosticsErrorf("duplicate output block for %q", name) | ||
143 | } | ||
144 | mod.Outputs[name] = o | ||
145 | } | ||
146 | } | ||
147 | |||
148 | for _, blockType := range []string{"resource", "data"} { | ||
149 | if resources := list.Filter(blockType); len(resources.Items) > 0 { | ||
150 | resources = resources.Children() | ||
151 | type ResourceBlock struct { | ||
152 | Provider string | ||
153 | } | ||
154 | |||
155 | for _, item := range resources.Items { | ||
156 | unwrapLegacyHCLObjectKeysFromJSON(item, 2) | ||
157 | |||
158 | if len(item.Keys) != 2 { | ||
159 | return nil, diagnosticsErrorf("resource block at %s has wrong label count", item.Pos()) | ||
160 | } | ||
161 | |||
162 | typeName := item.Keys[0].Token.Value().(string) | ||
163 | name := item.Keys[1].Token.Value().(string) | ||
164 | var mode ResourceMode | ||
165 | var rMap map[string]*Resource | ||
166 | switch blockType { | ||
167 | case "resource": | ||
168 | mode = ManagedResourceMode | ||
169 | rMap = mod.ManagedResources | ||
170 | case "data": | ||
171 | mode = DataResourceMode | ||
172 | rMap = mod.DataResources | ||
173 | } | ||
174 | |||
175 | var block ResourceBlock | ||
176 | err := legacyhcl.DecodeObject(&block, item.Val) | ||
177 | if err != nil { | ||
178 | return nil, diagnosticsErrorf("invalid resource block at %s: %s", item.Pos(), err) | ||
179 | } | ||
180 | |||
181 | var providerName, providerAlias string | ||
182 | if dotPos := strings.IndexByte(block.Provider, '.'); dotPos != -1 { | ||
183 | providerName = block.Provider[:dotPos] | ||
184 | providerAlias = block.Provider[dotPos+1:] | ||
185 | } else { | ||
186 | providerName = block.Provider | ||
187 | } | ||
188 | if providerName == "" { | ||
189 | providerName = resourceTypeDefaultProviderName(typeName) | ||
190 | } | ||
191 | |||
192 | r := &Resource{ | ||
193 | Mode: mode, | ||
194 | Type: typeName, | ||
195 | Name: name, | ||
196 | Provider: ProviderRef{ | ||
197 | Name: providerName, | ||
198 | Alias: providerAlias, | ||
199 | }, | ||
200 | Pos: sourcePosLegacyHCL(item.Pos(), filename), | ||
201 | } | ||
202 | key := r.MapKey() | ||
203 | if _, exists := rMap[key]; exists { | ||
204 | return nil, diagnosticsErrorf("duplicate resource block for %q", key) | ||
205 | } | ||
206 | rMap[key] = r | ||
207 | } | ||
208 | } | ||
209 | |||
210 | } | ||
211 | |||
212 | if moduleCalls := list.Filter("module"); len(moduleCalls.Items) > 0 { | ||
213 | moduleCalls = moduleCalls.Children() | ||
214 | type ModuleBlock struct { | ||
215 | Source string | ||
216 | Version string | ||
217 | } | ||
218 | |||
219 | for _, item := range moduleCalls.Items { | ||
220 | unwrapLegacyHCLObjectKeysFromJSON(item, 1) | ||
221 | |||
222 | if len(item.Keys) != 1 { | ||
223 | return nil, diagnosticsErrorf("module block at %s has no label", item.Pos()) | ||
224 | } | ||
225 | |||
226 | name := item.Keys[0].Token.Value().(string) | ||
227 | |||
228 | var block ModuleBlock | ||
229 | err := legacyhcl.DecodeObject(&block, item.Val) | ||
230 | if err != nil { | ||
231 | return nil, diagnosticsErrorf("module block at %s: %s", item.Pos(), err) | ||
232 | } | ||
233 | |||
234 | mc := &ModuleCall{ | ||
235 | Name: name, | ||
236 | Source: block.Source, | ||
237 | Version: block.Version, | ||
238 | Pos: sourcePosLegacyHCL(item.Pos(), filename), | ||
239 | } | ||
240 | // it's possible this module call is from an override file | ||
241 | if origMod, exists := mod.ModuleCalls[name]; exists { | ||
242 | if mc.Source == "" { | ||
243 | mc.Source = origMod.Source | ||
244 | } | ||
245 | } | ||
246 | mod.ModuleCalls[name] = mc | ||
247 | } | ||
248 | } | ||
249 | |||
250 | if providerConfigs := list.Filter("provider"); len(providerConfigs.Items) > 0 { | ||
251 | providerConfigs = providerConfigs.Children() | ||
252 | type ProviderBlock struct { | ||
253 | Version string | ||
254 | } | ||
255 | |||
256 | for _, item := range providerConfigs.Items { | ||
257 | unwrapLegacyHCLObjectKeysFromJSON(item, 1) | ||
258 | |||
259 | if len(item.Keys) != 1 { | ||
260 | return nil, diagnosticsErrorf("provider block at %s has no label", item.Pos()) | ||
261 | } | ||
262 | |||
263 | name := item.Keys[0].Token.Value().(string) | ||
264 | |||
265 | var block ProviderBlock | ||
266 | err := legacyhcl.DecodeObject(&block, item.Val) | ||
267 | if err != nil { | ||
268 | return nil, diagnosticsErrorf("invalid provider block at %s: %s", item.Pos(), err) | ||
269 | } | ||
270 | |||
271 | if block.Version != "" { | ||
272 | mod.RequiredProviders[name] = append(mod.RequiredProviders[name], block.Version) | ||
273 | } | ||
274 | |||
275 | // Even if there wasn't an explicit version required, we still | ||
276 | // need an entry in our map to signal the unversioned dependency. | ||
277 | if _, exists := mod.RequiredProviders[name]; !exists { | ||
278 | mod.RequiredProviders[name] = []string{} | ||
279 | } | ||
280 | |||
281 | } | ||
282 | } | ||
283 | } | ||
284 | |||
285 | return mod, nil | ||
286 | } | ||
287 | |||
288 | // unwrapLegacyHCLObjectKeysFromJSON cleans up an edge case that can occur when | ||
289 | // parsing JSON as input: if we're parsing JSON then directly nested | ||
290 | // items will show up as additional "keys". | ||
291 | // | ||
292 | // For objects that expect a fixed number of keys, this breaks the | ||
293 | // decoding process. This function unwraps the object into what it would've | ||
294 | // looked like if it came directly from HCL by specifying the number of keys | ||
295 | // you expect. | ||
296 | // | ||
297 | // Example: | ||
298 | // | ||
299 | // { "foo": { "baz": {} } } | ||
300 | // | ||
301 | // Will show up with Keys being: []string{"foo", "baz"} | ||
302 | // when we really just want the first two. This function will fix this. | ||
303 | func unwrapLegacyHCLObjectKeysFromJSON(item *legacyast.ObjectItem, depth int) { | ||
304 | if len(item.Keys) > depth && item.Keys[0].Token.JSON { | ||
305 | for len(item.Keys) > depth { | ||
306 | // Pop off the last key | ||
307 | n := len(item.Keys) | ||
308 | key := item.Keys[n-1] | ||
309 | item.Keys[n-1] = nil | ||
310 | item.Keys = item.Keys[:n-1] | ||
311 | |||
312 | // Wrap our value in a list | ||
313 | item.Val = &legacyast.ObjectType{ | ||
314 | List: &legacyast.ObjectList{ | ||
315 | Items: []*legacyast.ObjectItem{ | ||
316 | &legacyast.ObjectItem{ | ||
317 | Keys: []*legacyast.ObjectKey{key}, | ||
318 | Val: item.Val, | ||
319 | }, | ||
320 | }, | ||
321 | }, | ||
322 | } | ||
323 | } | ||
324 | } | ||
325 | } | ||