github.com/jsoriano/terraform@v0.6.7-0.20151026070445-8b70867fdd95/config/loader_hcl.go (about) 1 package config 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 7 "github.com/hashicorp/hcl" 8 hclobj "github.com/hashicorp/hcl/hcl" 9 "github.com/mitchellh/mapstructure" 10 ) 11 12 // hclConfigurable is an implementation of configurable that knows 13 // how to turn HCL configuration into a *Config object. 14 type hclConfigurable struct { 15 File string 16 Object *hclobj.Object 17 } 18 19 func (t *hclConfigurable) Config() (*Config, error) { 20 validKeys := map[string]struct{}{ 21 "atlas": struct{}{}, 22 "module": struct{}{}, 23 "output": struct{}{}, 24 "provider": struct{}{}, 25 "resource": struct{}{}, 26 "variable": struct{}{}, 27 } 28 29 type hclVariable struct { 30 Default interface{} 31 Description string 32 Fields []string `hcl:",decodedFields"` 33 } 34 35 var rawConfig struct { 36 Variable map[string]*hclVariable 37 } 38 39 if err := hcl.DecodeObject(&rawConfig, t.Object); err != nil { 40 return nil, err 41 } 42 43 // Start building up the actual configuration. We start with 44 // variables. 45 // TODO(mitchellh): Make function like loadVariablesHcl so that 46 // duplicates aren't overriden 47 config := new(Config) 48 if len(rawConfig.Variable) > 0 { 49 config.Variables = make([]*Variable, 0, len(rawConfig.Variable)) 50 for k, v := range rawConfig.Variable { 51 // Defaults turn into a slice of map[string]interface{} and 52 // we need to make sure to convert that down into the 53 // proper type for Config. 54 if ms, ok := v.Default.([]map[string]interface{}); ok { 55 def := make(map[string]interface{}) 56 for _, m := range ms { 57 for k, v := range m { 58 def[k] = v 59 } 60 } 61 62 v.Default = def 63 } 64 65 newVar := &Variable{ 66 Name: k, 67 Default: v.Default, 68 Description: v.Description, 69 } 70 71 config.Variables = append(config.Variables, newVar) 72 } 73 } 74 75 // Get Atlas configuration 76 if atlas := t.Object.Get("atlas", false); atlas != nil { 77 var err error 78 config.Atlas, err = loadAtlasHcl(atlas) 79 if err != nil { 80 return nil, err 81 } 82 } 83 84 // Build the modules 85 if modules := t.Object.Get("module", false); modules != nil { 86 var err error 87 config.Modules, err = loadModulesHcl(modules) 88 if err != nil { 89 return nil, err 90 } 91 } 92 93 // Build the provider configs 94 if providers := t.Object.Get("provider", false); providers != nil { 95 var err error 96 config.ProviderConfigs, err = loadProvidersHcl(providers) 97 if err != nil { 98 return nil, err 99 } 100 } 101 102 // Build the resources 103 if resources := t.Object.Get("resource", false); resources != nil { 104 var err error 105 config.Resources, err = loadResourcesHcl(resources) 106 if err != nil { 107 return nil, err 108 } 109 } 110 111 // Build the outputs 112 if outputs := t.Object.Get("output", false); outputs != nil { 113 var err error 114 config.Outputs, err = loadOutputsHcl(outputs) 115 if err != nil { 116 return nil, err 117 } 118 } 119 120 // Check for invalid keys 121 for _, elem := range t.Object.Elem(true) { 122 k := elem.Key 123 if _, ok := validKeys[k]; ok { 124 continue 125 } 126 127 config.unknownKeys = append(config.unknownKeys, k) 128 } 129 130 return config, nil 131 } 132 133 // loadFileHcl is a fileLoaderFunc that knows how to read HCL 134 // files and turn them into hclConfigurables. 135 func loadFileHcl(root string) (configurable, []string, error) { 136 var obj *hclobj.Object = nil 137 138 // Read the HCL file and prepare for parsing 139 d, err := ioutil.ReadFile(root) 140 if err != nil { 141 return nil, nil, fmt.Errorf( 142 "Error reading %s: %s", root, err) 143 } 144 145 // Parse it 146 obj, err = hcl.Parse(string(d)) 147 if err != nil { 148 return nil, nil, fmt.Errorf( 149 "Error parsing %s: %s", root, err) 150 } 151 152 // Start building the result 153 result := &hclConfigurable{ 154 File: root, 155 Object: obj, 156 } 157 158 // Dive in, find the imports. This is disabled for now since 159 // imports were removed prior to Terraform 0.1. The code is 160 // remaining here commented for historical purposes. 161 /* 162 imports := obj.Get("import") 163 if imports == nil { 164 result.Object.Ref() 165 return result, nil, nil 166 } 167 168 if imports.Type() != libucl.ObjectTypeString { 169 imports.Close() 170 171 return nil, nil, fmt.Errorf( 172 "Error in %s: all 'import' declarations should be in the format\n"+ 173 "`import \"foo\"` (Got type %s)", 174 root, 175 imports.Type()) 176 } 177 178 // Gather all the import paths 179 importPaths := make([]string, 0, imports.Len()) 180 iter := imports.Iterate(false) 181 for imp := iter.Next(); imp != nil; imp = iter.Next() { 182 path := imp.ToString() 183 if !filepath.IsAbs(path) { 184 // Relative paths are relative to the Terraform file itself 185 dir := filepath.Dir(root) 186 path = filepath.Join(dir, path) 187 } 188 189 importPaths = append(importPaths, path) 190 imp.Close() 191 } 192 iter.Close() 193 imports.Close() 194 195 result.Object.Ref() 196 */ 197 198 return result, nil, nil 199 } 200 201 // Given a handle to a HCL object, this transforms it into the Atlas 202 // configuration. 203 func loadAtlasHcl(obj *hclobj.Object) (*AtlasConfig, error) { 204 var config AtlasConfig 205 if err := hcl.DecodeObject(&config, obj); err != nil { 206 return nil, fmt.Errorf( 207 "Error reading atlas config: %s", 208 err) 209 } 210 211 return &config, nil 212 } 213 214 // Given a handle to a HCL object, this recurses into the structure 215 // and pulls out a list of modules. 216 // 217 // The resulting modules may not be unique, but each module 218 // represents exactly one module definition in the HCL configuration. 219 // We leave it up to another pass to merge them together. 220 func loadModulesHcl(os *hclobj.Object) ([]*Module, error) { 221 var allNames []*hclobj.Object 222 223 // See loadResourcesHcl for why this exists. Don't touch this. 224 for _, o1 := range os.Elem(false) { 225 // Iterate the inner to get the list of types 226 for _, o2 := range o1.Elem(true) { 227 // Iterate all of this type to get _all_ the types 228 for _, o3 := range o2.Elem(false) { 229 allNames = append(allNames, o3) 230 } 231 } 232 } 233 234 // Where all the results will go 235 var result []*Module 236 237 // Now go over all the types and their children in order to get 238 // all of the actual resources. 239 for _, obj := range allNames { 240 k := obj.Key 241 242 var config map[string]interface{} 243 if err := hcl.DecodeObject(&config, obj); err != nil { 244 return nil, fmt.Errorf( 245 "Error reading config for %s: %s", 246 k, 247 err) 248 } 249 250 // Remove the fields we handle specially 251 delete(config, "source") 252 253 rawConfig, err := NewRawConfig(config) 254 if err != nil { 255 return nil, fmt.Errorf( 256 "Error reading config for %s: %s", 257 k, 258 err) 259 } 260 261 // If we have a count, then figure it out 262 var source string 263 if o := obj.Get("source", false); o != nil { 264 err = hcl.DecodeObject(&source, o) 265 if err != nil { 266 return nil, fmt.Errorf( 267 "Error parsing source for %s: %s", 268 k, 269 err) 270 } 271 } 272 273 result = append(result, &Module{ 274 Name: k, 275 Source: source, 276 RawConfig: rawConfig, 277 }) 278 } 279 280 return result, nil 281 } 282 283 // LoadOutputsHcl recurses into the given HCL object and turns 284 // it into a mapping of outputs. 285 func loadOutputsHcl(os *hclobj.Object) ([]*Output, error) { 286 objects := make(map[string]*hclobj.Object) 287 288 // Iterate over all the "output" blocks and get the keys along with 289 // their raw configuration objects. We'll parse those later. 290 for _, o1 := range os.Elem(false) { 291 for _, o2 := range o1.Elem(true) { 292 objects[o2.Key] = o2 293 } 294 } 295 296 if len(objects) == 0 { 297 return nil, nil 298 } 299 300 // Go through each object and turn it into an actual result. 301 result := make([]*Output, 0, len(objects)) 302 for n, o := range objects { 303 var config map[string]interface{} 304 305 if err := hcl.DecodeObject(&config, o); err != nil { 306 return nil, err 307 } 308 309 rawConfig, err := NewRawConfig(config) 310 if err != nil { 311 return nil, fmt.Errorf( 312 "Error reading config for output %s: %s", 313 n, 314 err) 315 } 316 317 result = append(result, &Output{ 318 Name: n, 319 RawConfig: rawConfig, 320 }) 321 } 322 323 return result, nil 324 } 325 326 // LoadProvidersHcl recurses into the given HCL object and turns 327 // it into a mapping of provider configs. 328 func loadProvidersHcl(os *hclobj.Object) ([]*ProviderConfig, error) { 329 var objects []*hclobj.Object 330 331 // Iterate over all the "provider" blocks and get the keys along with 332 // their raw configuration objects. We'll parse those later. 333 for _, o1 := range os.Elem(false) { 334 for _, o2 := range o1.Elem(true) { 335 objects = append(objects, o2) 336 } 337 } 338 339 if len(objects) == 0 { 340 return nil, nil 341 } 342 343 // Go through each object and turn it into an actual result. 344 result := make([]*ProviderConfig, 0, len(objects)) 345 for _, o := range objects { 346 var config map[string]interface{} 347 348 if err := hcl.DecodeObject(&config, o); err != nil { 349 return nil, err 350 } 351 352 delete(config, "alias") 353 354 rawConfig, err := NewRawConfig(config) 355 if err != nil { 356 return nil, fmt.Errorf( 357 "Error reading config for provider config %s: %s", 358 o.Key, 359 err) 360 } 361 362 // If we have an alias field, then add those in 363 var alias string 364 if a := o.Get("alias", false); a != nil { 365 err := hcl.DecodeObject(&alias, a) 366 if err != nil { 367 return nil, fmt.Errorf( 368 "Error reading alias for provider[%s]: %s", 369 o.Key, 370 err) 371 } 372 } 373 374 result = append(result, &ProviderConfig{ 375 Name: o.Key, 376 Alias: alias, 377 RawConfig: rawConfig, 378 }) 379 } 380 381 return result, nil 382 } 383 384 // Given a handle to a HCL object, this recurses into the structure 385 // and pulls out a list of resources. 386 // 387 // The resulting resources may not be unique, but each resource 388 // represents exactly one resource definition in the HCL configuration. 389 // We leave it up to another pass to merge them together. 390 func loadResourcesHcl(os *hclobj.Object) ([]*Resource, error) { 391 var allTypes []*hclobj.Object 392 393 // HCL object iteration is really nasty. Below is likely to make 394 // no sense to anyone approaching this code. Luckily, it is very heavily 395 // tested. If working on a bug fix or feature, we recommend writing a 396 // test first then doing whatever you want to the code below. If you 397 // break it, the tests will catch it. Likewise, if you change this, 398 // MAKE SURE you write a test for your change, because its fairly impossible 399 // to reason about this mess. 400 // 401 // Functionally, what the code does below is get the libucl.Objects 402 // for all the TYPES, such as "aws_security_group". 403 for _, o1 := range os.Elem(false) { 404 // Iterate the inner to get the list of types 405 for _, o2 := range o1.Elem(true) { 406 // Iterate all of this type to get _all_ the types 407 for _, o3 := range o2.Elem(false) { 408 allTypes = append(allTypes, o3) 409 } 410 } 411 } 412 413 // Where all the results will go 414 var result []*Resource 415 416 // Now go over all the types and their children in order to get 417 // all of the actual resources. 418 for _, t := range allTypes { 419 for _, obj := range t.Elem(true) { 420 k := obj.Key 421 422 var config map[string]interface{} 423 if err := hcl.DecodeObject(&config, obj); err != nil { 424 return nil, fmt.Errorf( 425 "Error reading config for %s[%s]: %s", 426 t.Key, 427 k, 428 err) 429 } 430 431 // Remove the fields we handle specially 432 delete(config, "connection") 433 delete(config, "count") 434 delete(config, "depends_on") 435 delete(config, "provisioner") 436 delete(config, "provider") 437 delete(config, "lifecycle") 438 439 rawConfig, err := NewRawConfig(config) 440 if err != nil { 441 return nil, fmt.Errorf( 442 "Error reading config for %s[%s]: %s", 443 t.Key, 444 k, 445 err) 446 } 447 448 // If we have a count, then figure it out 449 var count string = "1" 450 if o := obj.Get("count", false); o != nil { 451 err = hcl.DecodeObject(&count, o) 452 if err != nil { 453 return nil, fmt.Errorf( 454 "Error parsing count for %s[%s]: %s", 455 t.Key, 456 k, 457 err) 458 } 459 } 460 countConfig, err := NewRawConfig(map[string]interface{}{ 461 "count": count, 462 }) 463 if err != nil { 464 return nil, err 465 } 466 countConfig.Key = "count" 467 468 // If we have depends fields, then add those in 469 var dependsOn []string 470 if o := obj.Get("depends_on", false); o != nil { 471 err := hcl.DecodeObject(&dependsOn, o) 472 if err != nil { 473 return nil, fmt.Errorf( 474 "Error reading depends_on for %s[%s]: %s", 475 t.Key, 476 k, 477 err) 478 } 479 } 480 481 // If we have connection info, then parse those out 482 var connInfo map[string]interface{} 483 if o := obj.Get("connection", false); o != nil { 484 err := hcl.DecodeObject(&connInfo, o) 485 if err != nil { 486 return nil, fmt.Errorf( 487 "Error reading connection info for %s[%s]: %s", 488 t.Key, 489 k, 490 err) 491 } 492 } 493 494 // If we have provisioners, then parse those out 495 var provisioners []*Provisioner 496 if os := obj.Get("provisioner", false); os != nil { 497 var err error 498 provisioners, err = loadProvisionersHcl(os, connInfo) 499 if err != nil { 500 return nil, fmt.Errorf( 501 "Error reading provisioners for %s[%s]: %s", 502 t.Key, 503 k, 504 err) 505 } 506 } 507 508 // If we have a provider, then parse it out 509 var provider string 510 if o := obj.Get("provider", false); o != nil { 511 err := hcl.DecodeObject(&provider, o) 512 if err != nil { 513 return nil, fmt.Errorf( 514 "Error reading provider for %s[%s]: %s", 515 t.Key, 516 k, 517 err) 518 } 519 } 520 521 // Check if the resource should be re-created before 522 // destroying the existing instance 523 var lifecycle ResourceLifecycle 524 if o := obj.Get("lifecycle", false); o != nil { 525 var raw map[string]interface{} 526 if err = hcl.DecodeObject(&raw, o); err != nil { 527 return nil, fmt.Errorf( 528 "Error parsing lifecycle for %s[%s]: %s", 529 t.Key, 530 k, 531 err) 532 } 533 534 if err := mapstructure.WeakDecode(raw, &lifecycle); err != nil { 535 return nil, fmt.Errorf( 536 "Error parsing lifecycle for %s[%s]: %s", 537 t.Key, 538 k, 539 err) 540 } 541 } 542 543 result = append(result, &Resource{ 544 Name: k, 545 Type: t.Key, 546 RawCount: countConfig, 547 RawConfig: rawConfig, 548 Provisioners: provisioners, 549 Provider: provider, 550 DependsOn: dependsOn, 551 Lifecycle: lifecycle, 552 }) 553 } 554 } 555 556 return result, nil 557 } 558 559 func loadProvisionersHcl(os *hclobj.Object, connInfo map[string]interface{}) ([]*Provisioner, error) { 560 pos := make([]*hclobj.Object, 0, int(os.Len())) 561 562 // Accumulate all the actual provisioner configuration objects. We 563 // have to iterate twice here: 564 // 565 // 1. The first iteration is of the list of `provisioner` blocks. 566 // 2. The second iteration is of the dictionary within the 567 // provisioner which will have only one element which is the 568 // type of provisioner to use along with tis config. 569 // 570 // In JSON it looks kind of like this: 571 // 572 // [ 573 // { 574 // "shell": { 575 // ... 576 // } 577 // } 578 // ] 579 // 580 for _, o1 := range os.Elem(false) { 581 for _, o2 := range o1.Elem(true) { 582 583 switch o1.Type { 584 case hclobj.ValueTypeList: 585 for _, o3 := range o2.Elem(true) { 586 pos = append(pos, o3) 587 } 588 case hclobj.ValueTypeObject: 589 pos = append(pos, o2) 590 } 591 } 592 } 593 594 // Short-circuit if there are no items 595 if len(pos) == 0 { 596 return nil, nil 597 } 598 599 result := make([]*Provisioner, 0, len(pos)) 600 for _, po := range pos { 601 var config map[string]interface{} 602 if err := hcl.DecodeObject(&config, po); err != nil { 603 return nil, err 604 } 605 606 // Delete the "connection" section, handle separately 607 delete(config, "connection") 608 609 rawConfig, err := NewRawConfig(config) 610 if err != nil { 611 return nil, err 612 } 613 614 // Check if we have a provisioner-level connection 615 // block that overrides the resource-level 616 var subConnInfo map[string]interface{} 617 if o := po.Get("connection", false); o != nil { 618 err := hcl.DecodeObject(&subConnInfo, o) 619 if err != nil { 620 return nil, err 621 } 622 } 623 624 // Inherit from the resource connInfo any keys 625 // that are not explicitly overriden. 626 if connInfo != nil && subConnInfo != nil { 627 for k, v := range connInfo { 628 if _, ok := subConnInfo[k]; !ok { 629 subConnInfo[k] = v 630 } 631 } 632 } else if subConnInfo == nil { 633 subConnInfo = connInfo 634 } 635 636 // Parse the connInfo 637 connRaw, err := NewRawConfig(subConnInfo) 638 if err != nil { 639 return nil, err 640 } 641 642 result = append(result, &Provisioner{ 643 Type: po.Key, 644 RawConfig: rawConfig, 645 ConnInfo: connRaw, 646 }) 647 } 648 649 return result, nil 650 } 651 652 /* 653 func hclObjectMap(os *hclobj.Object) map[string]ast.ListNode { 654 objects := make(map[string][]*hclobj.Object) 655 656 for _, o := range os.Elem(false) { 657 for _, elem := range o.Elem(true) { 658 val, ok := objects[elem.Key] 659 if !ok { 660 val = make([]*hclobj.Object, 0, 1) 661 } 662 663 val = append(val, elem) 664 objects[elem.Key] = val 665 } 666 } 667 668 return objects 669 } 670 */