github.com/partitio/terraform@v0.11.12-beta1/config/loader_hcl2.go (about) 1 package config 2 3 import ( 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. 17 type hcl2Configurable struct { 18 SourceFilename string 19 Body hcl2.Body 20 } 21 22 // hcl2Loader is a wrapper around a HCL parser that provides a fileLoaderFunc. 23 type 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. 31 var 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. 39 func 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. 47 func (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 68 func (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 }