aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/terraform/config/loader_hcl2.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/config/loader_hcl2.go')
-rw-r--r--vendor/github.com/hashicorp/terraform/config/loader_hcl2.go473
1 files changed, 473 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/config/loader_hcl2.go b/vendor/github.com/hashicorp/terraform/config/loader_hcl2.go
new file mode 100644
index 0000000..4f9f129
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/config/loader_hcl2.go
@@ -0,0 +1,473 @@
1package config
2
3import (
4 "fmt"
5 "sort"
6 "strings"
7
8 gohcl2 "github.com/hashicorp/hcl2/gohcl"
9 hcl2 "github.com/hashicorp/hcl2/hcl"
10 hcl2parse "github.com/hashicorp/hcl2/hclparse"
11 "github.com/hashicorp/terraform/config/hcl2shim"
12 "github.com/zclconf/go-cty/cty"
13)
14
15// hcl2Configurable is an implementation of configurable that knows
16// how to turn a HCL Body into a *Config object.
17type hcl2Configurable struct {
18 SourceFilename string
19 Body hcl2.Body
20}
21
22// hcl2Loader is a wrapper around a HCL parser that provides a fileLoaderFunc.
23type hcl2Loader struct {
24 Parser *hcl2parse.Parser
25}
26
27// For the moment we'll just have a global loader since we don't have anywhere
28// better to stash this.
29// TODO: refactor the loader API so that it uses some sort of object we can
30// stash the parser inside.
31var globalHCL2Loader = newHCL2Loader()
32
33// newHCL2Loader creates a new hcl2Loader containing a new HCL Parser.
34//
35// HCL parsers retain information about files that are loaded to aid in
36// producing diagnostic messages, so all files within a single configuration
37// should be loaded with the same parser to ensure the availability of
38// full diagnostic information.
39func newHCL2Loader() hcl2Loader {
40 return hcl2Loader{
41 Parser: hcl2parse.NewParser(),
42 }
43}
44
45// loadFile is a fileLoaderFunc that knows how to read a HCL2 file and turn it
46// into a hcl2Configurable.
47func (l hcl2Loader) loadFile(filename string) (configurable, []string, error) {
48 var f *hcl2.File
49 var diags hcl2.Diagnostics
50 if strings.HasSuffix(filename, ".json") {
51 f, diags = l.Parser.ParseJSONFile(filename)
52 } else {
53 f, diags = l.Parser.ParseHCLFile(filename)
54 }
55 if diags.HasErrors() {
56 // Return diagnostics as an error; callers may type-assert this to
57 // recover the original diagnostics, if it doesn't end up wrapped
58 // in another error.
59 return nil, nil, diags
60 }
61
62 return &hcl2Configurable{
63 SourceFilename: filename,
64 Body: f.Body,
65 }, nil, nil
66}
67
68func (t *hcl2Configurable) Config() (*Config, error) {
69 config := &Config{}
70
71 // these structs are used only for the initial shallow decoding; we'll
72 // expand this into the main, public-facing config structs afterwards.
73 type atlas struct {
74 Name string `hcl:"name"`
75 Include *[]string `hcl:"include"`
76 Exclude *[]string `hcl:"exclude"`
77 }
78 type provider struct {
79 Name string `hcl:"name,label"`
80 Alias *string `hcl:"alias,attr"`
81 Version *string `hcl:"version,attr"`
82 Config hcl2.Body `hcl:",remain"`
83 }
84 type module struct {
85 Name string `hcl:"name,label"`
86 Source string `hcl:"source,attr"`
87 Version *string `hcl:"version,attr"`
88 Providers *map[string]string `hcl:"providers,attr"`
89 Config hcl2.Body `hcl:",remain"`
90 }
91 type resourceLifecycle struct {
92 CreateBeforeDestroy *bool `hcl:"create_before_destroy,attr"`
93 PreventDestroy *bool `hcl:"prevent_destroy,attr"`
94 IgnoreChanges *[]string `hcl:"ignore_changes,attr"`
95 }
96 type connection struct {
97 Config hcl2.Body `hcl:",remain"`
98 }
99 type provisioner struct {
100 Type string `hcl:"type,label"`
101
102 When *string `hcl:"when,attr"`
103 OnFailure *string `hcl:"on_failure,attr"`
104
105 Connection *connection `hcl:"connection,block"`
106 Config hcl2.Body `hcl:",remain"`
107 }
108 type managedResource struct {
109 Type string `hcl:"type,label"`
110 Name string `hcl:"name,label"`
111
112 CountExpr hcl2.Expression `hcl:"count,attr"`
113 Provider *string `hcl:"provider,attr"`
114 DependsOn *[]string `hcl:"depends_on,attr"`
115
116 Lifecycle *resourceLifecycle `hcl:"lifecycle,block"`
117 Provisioners []provisioner `hcl:"provisioner,block"`
118 Connection *connection `hcl:"connection,block"`
119
120 Config hcl2.Body `hcl:",remain"`
121 }
122 type dataResource struct {
123 Type string `hcl:"type,label"`
124 Name string `hcl:"name,label"`
125
126 CountExpr hcl2.Expression `hcl:"count,attr"`
127 Provider *string `hcl:"provider,attr"`
128 DependsOn *[]string `hcl:"depends_on,attr"`
129
130 Config hcl2.Body `hcl:",remain"`
131 }
132 type variable struct {
133 Name string `hcl:"name,label"`
134
135 DeclaredType *string `hcl:"type,attr"`
136 Default *cty.Value `hcl:"default,attr"`
137 Description *string `hcl:"description,attr"`
138 Sensitive *bool `hcl:"sensitive,attr"`
139 }
140 type output struct {
141 Name string `hcl:"name,label"`
142
143 ValueExpr hcl2.Expression `hcl:"value,attr"`
144 DependsOn *[]string `hcl:"depends_on,attr"`
145 Description *string `hcl:"description,attr"`
146 Sensitive *bool `hcl:"sensitive,attr"`
147 }
148 type locals struct {
149 Definitions hcl2.Attributes `hcl:",remain"`
150 }
151 type backend struct {
152 Type string `hcl:"type,label"`
153 Config hcl2.Body `hcl:",remain"`
154 }
155 type terraform struct {
156 RequiredVersion *string `hcl:"required_version,attr"`
157 Backend *backend `hcl:"backend,block"`
158 }
159 type topLevel struct {
160 Atlas *atlas `hcl:"atlas,block"`
161 Datas []dataResource `hcl:"data,block"`
162 Modules []module `hcl:"module,block"`
163 Outputs []output `hcl:"output,block"`
164 Providers []provider `hcl:"provider,block"`
165 Resources []managedResource `hcl:"resource,block"`
166 Terraform *terraform `hcl:"terraform,block"`
167 Variables []variable `hcl:"variable,block"`
168 Locals []*locals `hcl:"locals,block"`
169 }
170
171 var raw topLevel
172 diags := gohcl2.DecodeBody(t.Body, nil, &raw)
173 if diags.HasErrors() {
174 // Do some minimal decoding to see if we can at least get the
175 // required Terraform version, which might help explain why we
176 // couldn't parse the rest.
177 if raw.Terraform != nil && raw.Terraform.RequiredVersion != nil {
178 config.Terraform = &Terraform{
179 RequiredVersion: *raw.Terraform.RequiredVersion,
180 }
181 }
182
183 // We return the diags as an implementation of error, which the
184 // caller than then type-assert if desired to recover the individual
185 // diagnostics.
186 // FIXME: The current API gives us no way to return warnings in the
187 // absense of any errors.
188 return config, diags
189 }
190
191 if raw.Terraform != nil {
192 var reqdVersion string
193 var backend *Backend
194
195 if raw.Terraform.RequiredVersion != nil {
196 reqdVersion = *raw.Terraform.RequiredVersion
197 }
198 if raw.Terraform.Backend != nil {
199 backend = new(Backend)
200 backend.Type = raw.Terraform.Backend.Type
201
202 // We don't permit interpolations or nested blocks inside the
203 // backend config, so we can decode the config early here and
204 // get direct access to the values, which is important for the
205 // config hashing to work as expected.
206 var config map[string]string
207 configDiags := gohcl2.DecodeBody(raw.Terraform.Backend.Config, nil, &config)
208 diags = append(diags, configDiags...)
209
210 raw := make(map[string]interface{}, len(config))
211 for k, v := range config {
212 raw[k] = v
213 }
214
215 var err error
216 backend.RawConfig, err = NewRawConfig(raw)
217 if err != nil {
218 diags = append(diags, &hcl2.Diagnostic{
219 Severity: hcl2.DiagError,
220 Summary: "Invalid backend configuration",
221 Detail: fmt.Sprintf("Error in backend configuration: %s", err),
222 })
223 }
224 }
225
226 config.Terraform = &Terraform{
227 RequiredVersion: reqdVersion,
228 Backend: backend,
229 }
230 }
231
232 if raw.Atlas != nil {
233 var include, exclude []string
234 if raw.Atlas.Include != nil {
235 include = *raw.Atlas.Include
236 }
237 if raw.Atlas.Exclude != nil {
238 exclude = *raw.Atlas.Exclude
239 }
240 config.Atlas = &AtlasConfig{
241 Name: raw.Atlas.Name,
242 Include: include,
243 Exclude: exclude,
244 }
245 }
246
247 for _, rawM := range raw.Modules {
248 m := &Module{
249 Name: rawM.Name,
250 Source: rawM.Source,
251 RawConfig: NewRawConfigHCL2(rawM.Config),
252 }
253
254 if rawM.Version != nil {
255 m.Version = *rawM.Version
256 }
257
258 if rawM.Providers != nil {
259 m.Providers = *rawM.Providers
260 }
261
262 config.Modules = append(config.Modules, m)
263 }
264
265 for _, rawV := range raw.Variables {
266 v := &Variable{
267 Name: rawV.Name,
268 }
269 if rawV.DeclaredType != nil {
270 v.DeclaredType = *rawV.DeclaredType
271 }
272 if rawV.Default != nil {
273 v.Default = hcl2shim.ConfigValueFromHCL2(*rawV.Default)
274 }
275 if rawV.Description != nil {
276 v.Description = *rawV.Description
277 }
278
279 config.Variables = append(config.Variables, v)
280 }
281
282 for _, rawO := range raw.Outputs {
283 o := &Output{
284 Name: rawO.Name,
285 }
286
287 if rawO.Description != nil {
288 o.Description = *rawO.Description
289 }
290 if rawO.DependsOn != nil {
291 o.DependsOn = *rawO.DependsOn
292 }
293 if rawO.Sensitive != nil {
294 o.Sensitive = *rawO.Sensitive
295 }
296
297 // The result is expected to be a map like map[string]interface{}{"value": something},
298 // so we'll fake that with our hcl2shim.SingleAttrBody shim.
299 o.RawConfig = NewRawConfigHCL2(hcl2shim.SingleAttrBody{
300 Name: "value",
301 Expr: rawO.ValueExpr,
302 })
303
304 config.Outputs = append(config.Outputs, o)
305 }
306
307 for _, rawR := range raw.Resources {
308 r := &Resource{
309 Mode: ManagedResourceMode,
310 Type: rawR.Type,
311 Name: rawR.Name,
312 }
313 if rawR.Lifecycle != nil {
314 var l ResourceLifecycle
315 if rawR.Lifecycle.CreateBeforeDestroy != nil {
316 l.CreateBeforeDestroy = *rawR.Lifecycle.CreateBeforeDestroy
317 }
318 if rawR.Lifecycle.PreventDestroy != nil {
319 l.PreventDestroy = *rawR.Lifecycle.PreventDestroy
320 }
321 if rawR.Lifecycle.IgnoreChanges != nil {
322 l.IgnoreChanges = *rawR.Lifecycle.IgnoreChanges
323 }
324 r.Lifecycle = l
325 }
326 if rawR.Provider != nil {
327 r.Provider = *rawR.Provider
328 }
329 if rawR.DependsOn != nil {
330 r.DependsOn = *rawR.DependsOn
331 }
332
333 var defaultConnInfo *RawConfig
334 if rawR.Connection != nil {
335 defaultConnInfo = NewRawConfigHCL2(rawR.Connection.Config)
336 }
337
338 for _, rawP := range rawR.Provisioners {
339 p := &Provisioner{
340 Type: rawP.Type,
341 }
342
343 switch {
344 case rawP.When == nil:
345 p.When = ProvisionerWhenCreate
346 case *rawP.When == "create":
347 p.When = ProvisionerWhenCreate
348 case *rawP.When == "destroy":
349 p.When = ProvisionerWhenDestroy
350 default:
351 p.When = ProvisionerWhenInvalid
352 }
353
354 switch {
355 case rawP.OnFailure == nil:
356 p.OnFailure = ProvisionerOnFailureFail
357 case *rawP.When == "fail":
358 p.OnFailure = ProvisionerOnFailureFail
359 case *rawP.When == "continue":
360 p.OnFailure = ProvisionerOnFailureContinue
361 default:
362 p.OnFailure = ProvisionerOnFailureInvalid
363 }
364
365 if rawP.Connection != nil {
366 p.ConnInfo = NewRawConfigHCL2(rawP.Connection.Config)
367 } else {
368 p.ConnInfo = defaultConnInfo
369 }
370
371 p.RawConfig = NewRawConfigHCL2(rawP.Config)
372
373 r.Provisioners = append(r.Provisioners, p)
374 }
375
376 // The old loader records the count expression as a weird RawConfig with
377 // a single-element map inside. Since the rest of the world is assuming
378 // that, we'll mimic it here.
379 {
380 countBody := hcl2shim.SingleAttrBody{
381 Name: "count",
382 Expr: rawR.CountExpr,
383 }
384
385 r.RawCount = NewRawConfigHCL2(countBody)
386 r.RawCount.Key = "count"
387 }
388
389 r.RawConfig = NewRawConfigHCL2(rawR.Config)
390
391 config.Resources = append(config.Resources, r)
392
393 }
394
395 for _, rawR := range raw.Datas {
396 r := &Resource{
397 Mode: DataResourceMode,
398 Type: rawR.Type,
399 Name: rawR.Name,
400 }
401
402 if rawR.Provider != nil {
403 r.Provider = *rawR.Provider
404 }
405 if rawR.DependsOn != nil {
406 r.DependsOn = *rawR.DependsOn
407 }
408
409 // The old loader records the count expression as a weird RawConfig with
410 // a single-element map inside. Since the rest of the world is assuming
411 // that, we'll mimic it here.
412 {
413 countBody := hcl2shim.SingleAttrBody{
414 Name: "count",
415 Expr: rawR.CountExpr,
416 }
417
418 r.RawCount = NewRawConfigHCL2(countBody)
419 r.RawCount.Key = "count"
420 }
421
422 r.RawConfig = NewRawConfigHCL2(rawR.Config)
423
424 config.Resources = append(config.Resources, r)
425 }
426
427 for _, rawP := range raw.Providers {
428 p := &ProviderConfig{
429 Name: rawP.Name,
430 }
431
432 if rawP.Alias != nil {
433 p.Alias = *rawP.Alias
434 }
435 if rawP.Version != nil {
436 p.Version = *rawP.Version
437 }
438
439 // The result is expected to be a map like map[string]interface{}{"value": something},
440 // so we'll fake that with our hcl2shim.SingleAttrBody shim.
441 p.RawConfig = NewRawConfigHCL2(rawP.Config)
442
443 config.ProviderConfigs = append(config.ProviderConfigs, p)
444 }
445
446 for _, rawL := range raw.Locals {
447 names := make([]string, 0, len(rawL.Definitions))
448 for n := range rawL.Definitions {
449 names = append(names, n)
450 }
451 sort.Strings(names)
452 for _, n := range names {
453 attr := rawL.Definitions[n]
454 l := &Local{
455 Name: n,
456 RawConfig: NewRawConfigHCL2(hcl2shim.SingleAttrBody{
457 Name: "value",
458 Expr: attr.Expr,
459 }),
460 }
461 config.Locals = append(config.Locals, l)
462 }
463 }
464
465 // FIXME: The current API gives us no way to return warnings in the
466 // absense of any errors.
467 var err error
468 if diags.HasErrors() {
469 err = diags
470 }
471
472 return config, err
473}