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