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"
15 // hcl2Configurable is an implementation of configurable that knows
16 // how to turn a HCL Body into a *Config object.
17 type hcl2Configurable struct {
22 // hcl2Loader is a wrapper around a HCL parser that provides a fileLoaderFunc.
23 type hcl2Loader struct {
24 Parser *hcl2parse.Parser
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.
31 var globalHCL2Loader = newHCL2Loader()
33 // newHCL2Loader creates a new hcl2Loader containing a new HCL Parser.
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.
39 func newHCL2Loader() hcl2Loader {
41 Parser: hcl2parse.NewParser(),
45 // loadFile is a fileLoaderFunc that knows how to read a HCL2 file and turn it
46 // into a hcl2Configurable.
47 func (l hcl2Loader) loadFile(filename string) (configurable, []string, error) {
49 var diags hcl2.Diagnostics
50 if strings.HasSuffix(filename, ".json") {
51 f, diags = l.Parser.ParseJSONFile(filename)
53 f, diags = l.Parser.ParseHCLFile(filename)
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
59 return nil, nil, diags
62 return &hcl2Configurable{
63 SourceFilename: filename,
68 func (t *hcl2Configurable) Config() (*Config, error) {
71 // these structs are used only for the initial shallow decoding; we'll
72 // expand this into the main, public-facing config structs afterwards.
74 Name string `hcl:"name"`
75 Include *[]string `hcl:"include"`
76 Exclude *[]string `hcl:"exclude"`
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"`
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"`
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"`
96 type connection struct {
97 Config hcl2.Body `hcl:",remain"`
99 type provisioner struct {
100 Type string `hcl:"type,label"`
102 When *string `hcl:"when,attr"`
103 OnFailure *string `hcl:"on_failure,attr"`
105 Connection *connection `hcl:"connection,block"`
106 Config hcl2.Body `hcl:",remain"`
108 type managedResource struct {
109 Type string `hcl:"type,label"`
110 Name string `hcl:"name,label"`
112 CountExpr hcl2.Expression `hcl:"count,attr"`
113 Provider *string `hcl:"provider,attr"`
114 DependsOn *[]string `hcl:"depends_on,attr"`
116 Lifecycle *resourceLifecycle `hcl:"lifecycle,block"`
117 Provisioners []provisioner `hcl:"provisioner,block"`
118 Connection *connection `hcl:"connection,block"`
120 Config hcl2.Body `hcl:",remain"`
122 type dataResource struct {
123 Type string `hcl:"type,label"`
124 Name string `hcl:"name,label"`
126 CountExpr hcl2.Expression `hcl:"count,attr"`
127 Provider *string `hcl:"provider,attr"`
128 DependsOn *[]string `hcl:"depends_on,attr"`
130 Config hcl2.Body `hcl:",remain"`
132 type variable struct {
133 Name string `hcl:"name,label"`
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"`
141 Name string `hcl:"name,label"`
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"`
149 Definitions hcl2.Attributes `hcl:",remain"`
151 type backend struct {
152 Type string `hcl:"type,label"`
153 Config hcl2.Body `hcl:",remain"`
155 type terraform struct {
156 RequiredVersion *string `hcl:"required_version,attr"`
157 Backend *backend `hcl:"backend,block"`
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"`
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,
183 // We return the diags as an implementation of error, which the
184 // caller than then type-assert if desired to recover the individual
186 // FIXME: The current API gives us no way to return warnings in the
187 // absense of any errors.
191 if raw.Terraform != nil {
192 var reqdVersion string
195 if raw.Terraform.RequiredVersion != nil {
196 reqdVersion = *raw.Terraform.RequiredVersion
198 if raw.Terraform.Backend != nil {
199 backend = new(Backend)
200 backend.Type = raw.Terraform.Backend.Type
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...)
210 raw := make(map[string]interface{}, len(config))
211 for k, v := range config {
216 backend.RawConfig, err = NewRawConfig(raw)
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),
226 config.Terraform = &Terraform{
227 RequiredVersion: reqdVersion,
232 if raw.Atlas != nil {
233 var include, exclude []string
234 if raw.Atlas.Include != nil {
235 include = *raw.Atlas.Include
237 if raw.Atlas.Exclude != nil {
238 exclude = *raw.Atlas.Exclude
240 config.Atlas = &AtlasConfig{
241 Name: raw.Atlas.Name,
247 for _, rawM := range raw.Modules {
251 RawConfig: NewRawConfigHCL2(rawM.Config),
254 if rawM.Version != nil {
255 m.Version = *rawM.Version
258 if rawM.Providers != nil {
259 m.Providers = *rawM.Providers
262 config.Modules = append(config.Modules, m)
265 for _, rawV := range raw.Variables {
269 if rawV.DeclaredType != nil {
270 v.DeclaredType = *rawV.DeclaredType
272 if rawV.Default != nil {
273 v.Default = hcl2shim.ConfigValueFromHCL2(*rawV.Default)
275 if rawV.Description != nil {
276 v.Description = *rawV.Description
279 config.Variables = append(config.Variables, v)
282 for _, rawO := range raw.Outputs {
287 if rawO.Description != nil {
288 o.Description = *rawO.Description
290 if rawO.DependsOn != nil {
291 o.DependsOn = *rawO.DependsOn
293 if rawO.Sensitive != nil {
294 o.Sensitive = *rawO.Sensitive
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{
301 Expr: rawO.ValueExpr,
304 config.Outputs = append(config.Outputs, o)
307 for _, rawR := range raw.Resources {
309 Mode: ManagedResourceMode,
313 if rawR.Lifecycle != nil {
314 var l ResourceLifecycle
315 if rawR.Lifecycle.CreateBeforeDestroy != nil {
316 l.CreateBeforeDestroy = *rawR.Lifecycle.CreateBeforeDestroy
318 if rawR.Lifecycle.PreventDestroy != nil {
319 l.PreventDestroy = *rawR.Lifecycle.PreventDestroy
321 if rawR.Lifecycle.IgnoreChanges != nil {
322 l.IgnoreChanges = *rawR.Lifecycle.IgnoreChanges
326 if rawR.Provider != nil {
327 r.Provider = *rawR.Provider
329 if rawR.DependsOn != nil {
330 r.DependsOn = *rawR.DependsOn
333 var defaultConnInfo *RawConfig
334 if rawR.Connection != nil {
335 defaultConnInfo = NewRawConfigHCL2(rawR.Connection.Config)
338 for _, rawP := range rawR.Provisioners {
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
351 p.When = ProvisionerWhenInvalid
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
362 p.OnFailure = ProvisionerOnFailureInvalid
365 if rawP.Connection != nil {
366 p.ConnInfo = NewRawConfigHCL2(rawP.Connection.Config)
368 p.ConnInfo = defaultConnInfo
371 p.RawConfig = NewRawConfigHCL2(rawP.Config)
373 r.Provisioners = append(r.Provisioners, p)
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.
380 countBody := hcl2shim.SingleAttrBody{
382 Expr: rawR.CountExpr,
385 r.RawCount = NewRawConfigHCL2(countBody)
386 r.RawCount.Key = "count"
389 r.RawConfig = NewRawConfigHCL2(rawR.Config)
391 config.Resources = append(config.Resources, r)
395 for _, rawR := range raw.Datas {
397 Mode: DataResourceMode,
402 if rawR.Provider != nil {
403 r.Provider = *rawR.Provider
405 if rawR.DependsOn != nil {
406 r.DependsOn = *rawR.DependsOn
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.
413 countBody := hcl2shim.SingleAttrBody{
415 Expr: rawR.CountExpr,
418 r.RawCount = NewRawConfigHCL2(countBody)
419 r.RawCount.Key = "count"
422 r.RawConfig = NewRawConfigHCL2(rawR.Config)
424 config.Resources = append(config.Resources, r)
427 for _, rawP := range raw.Providers {
428 p := &ProviderConfig{
432 if rawP.Alias != nil {
433 p.Alias = *rawP.Alias
435 if rawP.Version != nil {
436 p.Version = *rawP.Version
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)
443 config.ProviderConfigs = append(config.ProviderConfigs, p)
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)
452 for _, n := range names {
453 attr := rawL.Definitions[n]
456 RawConfig: NewRawConfigHCL2(hcl2shim.SingleAttrBody{
461 config.Locals = append(config.Locals, l)
465 // FIXME: The current API gives us no way to return warnings in the
466 // absense of any errors.
468 if diags.HasErrors() {