github.com/ojongerius/terraform@v0.7.1-0.20160811111335-97fcd5f4cc90/config/loader_hcl.go (about) 1 package config 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 7 "github.com/hashicorp/go-multierror" 8 "github.com/hashicorp/hcl" 9 "github.com/hashicorp/hcl/hcl/ast" 10 "github.com/mitchellh/mapstructure" 11 ) 12 13 // hclConfigurable is an implementation of configurable that knows 14 // how to turn HCL configuration into a *Config object. 15 type hclConfigurable struct { 16 File string 17 Root *ast.File 18 } 19 20 func (t *hclConfigurable) Config() (*Config, error) { 21 validKeys := map[string]struct{}{ 22 "atlas": struct{}{}, 23 "data": struct{}{}, 24 "module": struct{}{}, 25 "output": struct{}{}, 26 "provider": struct{}{}, 27 "resource": struct{}{}, 28 "variable": struct{}{}, 29 } 30 31 type hclVariable struct { 32 Default interface{} 33 Description string 34 DeclaredType string `hcl:"type"` 35 Fields []string `hcl:",decodedFields"` 36 } 37 38 var rawConfig struct { 39 Variable map[string]*hclVariable 40 } 41 42 // Top-level item should be the object list 43 list, ok := t.Root.Node.(*ast.ObjectList) 44 if !ok { 45 return nil, fmt.Errorf("error parsing: file doesn't contain a root object") 46 } 47 48 if err := hcl.DecodeObject(&rawConfig, list); err != nil { 49 return nil, err 50 } 51 52 // Start building up the actual configuration. We start with 53 // variables. 54 // TODO(mitchellh): Make function like loadVariablesHcl so that 55 // duplicates aren't overriden 56 config := new(Config) 57 if len(rawConfig.Variable) > 0 { 58 config.Variables = make([]*Variable, 0, len(rawConfig.Variable)) 59 for k, v := range rawConfig.Variable { 60 // Defaults turn into a slice of map[string]interface{} and 61 // we need to make sure to convert that down into the 62 // proper type for Config. 63 if ms, ok := v.Default.([]map[string]interface{}); ok { 64 def := make(map[string]interface{}) 65 for _, m := range ms { 66 for k, v := range m { 67 def[k] = v 68 } 69 } 70 71 v.Default = def 72 } 73 74 newVar := &Variable{ 75 Name: k, 76 DeclaredType: v.DeclaredType, 77 Default: v.Default, 78 Description: v.Description, 79 } 80 81 if err := newVar.ValidateTypeAndDefault(); err != nil { 82 return nil, err 83 } 84 85 config.Variables = append(config.Variables, newVar) 86 } 87 } 88 89 // Get Atlas configuration 90 if atlas := list.Filter("atlas"); len(atlas.Items) > 0 { 91 var err error 92 config.Atlas, err = loadAtlasHcl(atlas) 93 if err != nil { 94 return nil, err 95 } 96 } 97 98 // Build the modules 99 if modules := list.Filter("module"); len(modules.Items) > 0 { 100 var err error 101 config.Modules, err = loadModulesHcl(modules) 102 if err != nil { 103 return nil, err 104 } 105 } 106 107 // Build the provider configs 108 if providers := list.Filter("provider"); len(providers.Items) > 0 { 109 var err error 110 config.ProviderConfigs, err = loadProvidersHcl(providers) 111 if err != nil { 112 return nil, err 113 } 114 } 115 116 // Build the resources 117 { 118 var err error 119 managedResourceConfigs := list.Filter("resource") 120 dataResourceConfigs := list.Filter("data") 121 122 config.Resources = make( 123 []*Resource, 0, 124 len(managedResourceConfigs.Items)+len(dataResourceConfigs.Items), 125 ) 126 127 managedResources, err := loadManagedResourcesHcl(managedResourceConfigs) 128 if err != nil { 129 return nil, err 130 } 131 dataResources, err := loadDataResourcesHcl(dataResourceConfigs) 132 if err != nil { 133 return nil, err 134 } 135 136 config.Resources = append(config.Resources, dataResources...) 137 config.Resources = append(config.Resources, managedResources...) 138 } 139 140 // Build the outputs 141 if outputs := list.Filter("output"); len(outputs.Items) > 0 { 142 var err error 143 config.Outputs, err = loadOutputsHcl(outputs) 144 if err != nil { 145 return nil, err 146 } 147 } 148 149 // Check for invalid keys 150 for _, item := range list.Items { 151 if len(item.Keys) == 0 { 152 // Not sure how this would happen, but let's avoid a panic 153 continue 154 } 155 156 k := item.Keys[0].Token.Value().(string) 157 if _, ok := validKeys[k]; ok { 158 continue 159 } 160 161 config.unknownKeys = append(config.unknownKeys, k) 162 } 163 164 return config, nil 165 } 166 167 // loadFileHcl is a fileLoaderFunc that knows how to read HCL 168 // files and turn them into hclConfigurables. 169 func loadFileHcl(root string) (configurable, []string, error) { 170 // Read the HCL file and prepare for parsing 171 d, err := ioutil.ReadFile(root) 172 if err != nil { 173 return nil, nil, fmt.Errorf( 174 "Error reading %s: %s", root, err) 175 } 176 177 // Parse it 178 hclRoot, err := hcl.Parse(string(d)) 179 if err != nil { 180 return nil, nil, fmt.Errorf( 181 "Error parsing %s: %s", root, err) 182 } 183 184 // Start building the result 185 result := &hclConfigurable{ 186 File: root, 187 Root: hclRoot, 188 } 189 190 // Dive in, find the imports. This is disabled for now since 191 // imports were removed prior to Terraform 0.1. The code is 192 // remaining here commented for historical purposes. 193 /* 194 imports := obj.Get("import") 195 if imports == nil { 196 result.Object.Ref() 197 return result, nil, nil 198 } 199 200 if imports.Type() != libucl.ObjectTypeString { 201 imports.Close() 202 203 return nil, nil, fmt.Errorf( 204 "Error in %s: all 'import' declarations should be in the format\n"+ 205 "`import \"foo\"` (Got type %s)", 206 root, 207 imports.Type()) 208 } 209 210 // Gather all the import paths 211 importPaths := make([]string, 0, imports.Len()) 212 iter := imports.Iterate(false) 213 for imp := iter.Next(); imp != nil; imp = iter.Next() { 214 path := imp.ToString() 215 if !filepath.IsAbs(path) { 216 // Relative paths are relative to the Terraform file itself 217 dir := filepath.Dir(root) 218 path = filepath.Join(dir, path) 219 } 220 221 importPaths = append(importPaths, path) 222 imp.Close() 223 } 224 iter.Close() 225 imports.Close() 226 227 result.Object.Ref() 228 */ 229 230 return result, nil, nil 231 } 232 233 // Given a handle to a HCL object, this transforms it into the Atlas 234 // configuration. 235 func loadAtlasHcl(list *ast.ObjectList) (*AtlasConfig, error) { 236 if len(list.Items) > 1 { 237 return nil, fmt.Errorf("only one 'atlas' block allowed") 238 } 239 240 // Get our one item 241 item := list.Items[0] 242 243 var config AtlasConfig 244 if err := hcl.DecodeObject(&config, item.Val); err != nil { 245 return nil, fmt.Errorf( 246 "Error reading atlas config: %s", 247 err) 248 } 249 250 return &config, nil 251 } 252 253 // Given a handle to a HCL object, this recurses into the structure 254 // and pulls out a list of modules. 255 // 256 // The resulting modules may not be unique, but each module 257 // represents exactly one module definition in the HCL configuration. 258 // We leave it up to another pass to merge them together. 259 func loadModulesHcl(list *ast.ObjectList) ([]*Module, error) { 260 list = list.Children() 261 if len(list.Items) == 0 { 262 return nil, nil 263 } 264 265 // Where all the results will go 266 var result []*Module 267 268 // Now go over all the types and their children in order to get 269 // all of the actual resources. 270 for _, item := range list.Items { 271 k := item.Keys[0].Token.Value().(string) 272 273 var listVal *ast.ObjectList 274 if ot, ok := item.Val.(*ast.ObjectType); ok { 275 listVal = ot.List 276 } else { 277 return nil, fmt.Errorf("module '%s': should be an object", k) 278 } 279 280 var config map[string]interface{} 281 if err := hcl.DecodeObject(&config, item.Val); err != nil { 282 return nil, fmt.Errorf( 283 "Error reading config for %s: %s", 284 k, 285 err) 286 } 287 288 // Remove the fields we handle specially 289 delete(config, "source") 290 291 rawConfig, err := NewRawConfig(config) 292 if err != nil { 293 return nil, fmt.Errorf( 294 "Error reading config for %s: %s", 295 k, 296 err) 297 } 298 299 // If we have a count, then figure it out 300 var source string 301 if o := listVal.Filter("source"); len(o.Items) > 0 { 302 err = hcl.DecodeObject(&source, o.Items[0].Val) 303 if err != nil { 304 return nil, fmt.Errorf( 305 "Error parsing source for %s: %s", 306 k, 307 err) 308 } 309 } 310 311 result = append(result, &Module{ 312 Name: k, 313 Source: source, 314 RawConfig: rawConfig, 315 }) 316 } 317 318 return result, nil 319 } 320 321 // LoadOutputsHcl recurses into the given HCL object and turns 322 // it into a mapping of outputs. 323 func loadOutputsHcl(list *ast.ObjectList) ([]*Output, error) { 324 list = list.Children() 325 if len(list.Items) == 0 { 326 return nil, nil 327 } 328 329 // Go through each object and turn it into an actual result. 330 result := make([]*Output, 0, len(list.Items)) 331 for _, item := range list.Items { 332 n := item.Keys[0].Token.Value().(string) 333 334 var config map[string]interface{} 335 if err := hcl.DecodeObject(&config, item.Val); err != nil { 336 return nil, err 337 } 338 339 rawConfig, err := NewRawConfig(config) 340 if err != nil { 341 return nil, fmt.Errorf( 342 "Error reading config for output %s: %s", 343 n, 344 err) 345 } 346 347 result = append(result, &Output{ 348 Name: n, 349 RawConfig: rawConfig, 350 }) 351 } 352 353 return result, nil 354 } 355 356 // LoadProvidersHcl recurses into the given HCL object and turns 357 // it into a mapping of provider configs. 358 func loadProvidersHcl(list *ast.ObjectList) ([]*ProviderConfig, error) { 359 list = list.Children() 360 if len(list.Items) == 0 { 361 return nil, nil 362 } 363 364 // Go through each object and turn it into an actual result. 365 result := make([]*ProviderConfig, 0, len(list.Items)) 366 for _, item := range list.Items { 367 n := item.Keys[0].Token.Value().(string) 368 369 var listVal *ast.ObjectList 370 if ot, ok := item.Val.(*ast.ObjectType); ok { 371 listVal = ot.List 372 } else { 373 return nil, fmt.Errorf("module '%s': should be an object", n) 374 } 375 376 var config map[string]interface{} 377 if err := hcl.DecodeObject(&config, item.Val); err != nil { 378 return nil, err 379 } 380 381 delete(config, "alias") 382 383 rawConfig, err := NewRawConfig(config) 384 if err != nil { 385 return nil, fmt.Errorf( 386 "Error reading config for provider config %s: %s", 387 n, 388 err) 389 } 390 391 // If we have an alias field, then add those in 392 var alias string 393 if a := listVal.Filter("alias"); len(a.Items) > 0 { 394 err := hcl.DecodeObject(&alias, a.Items[0].Val) 395 if err != nil { 396 return nil, fmt.Errorf( 397 "Error reading alias for provider[%s]: %s", 398 n, 399 err) 400 } 401 } 402 403 result = append(result, &ProviderConfig{ 404 Name: n, 405 Alias: alias, 406 RawConfig: rawConfig, 407 }) 408 } 409 410 return result, nil 411 } 412 413 // Given a handle to a HCL object, this recurses into the structure 414 // and pulls out a list of data sources. 415 // 416 // The resulting data sources may not be unique, but each one 417 // represents exactly one data definition in the HCL configuration. 418 // We leave it up to another pass to merge them together. 419 func loadDataResourcesHcl(list *ast.ObjectList) ([]*Resource, error) { 420 list = list.Children() 421 if len(list.Items) == 0 { 422 return nil, nil 423 } 424 425 // Where all the results will go 426 var result []*Resource 427 428 // Now go over all the types and their children in order to get 429 // all of the actual resources. 430 for _, item := range list.Items { 431 if len(item.Keys) != 2 { 432 return nil, fmt.Errorf( 433 "position %s: 'data' must be followed by exactly two strings: a type and a name", 434 item.Pos()) 435 } 436 437 t := item.Keys[0].Token.Value().(string) 438 k := item.Keys[1].Token.Value().(string) 439 440 var listVal *ast.ObjectList 441 if ot, ok := item.Val.(*ast.ObjectType); ok { 442 listVal = ot.List 443 } else { 444 return nil, fmt.Errorf("data sources %s[%s]: should be an object", t, k) 445 } 446 447 var config map[string]interface{} 448 if err := hcl.DecodeObject(&config, item.Val); err != nil { 449 return nil, fmt.Errorf( 450 "Error reading config for %s[%s]: %s", 451 t, 452 k, 453 err) 454 } 455 456 // Remove the fields we handle specially 457 delete(config, "depends_on") 458 delete(config, "provider") 459 460 rawConfig, err := NewRawConfig(config) 461 if err != nil { 462 return nil, fmt.Errorf( 463 "Error reading config for %s[%s]: %s", 464 t, 465 k, 466 err) 467 } 468 469 // If we have a count, then figure it out 470 var count string = "1" 471 if o := listVal.Filter("count"); len(o.Items) > 0 { 472 err = hcl.DecodeObject(&count, o.Items[0].Val) 473 if err != nil { 474 return nil, fmt.Errorf( 475 "Error parsing count for %s[%s]: %s", 476 t, 477 k, 478 err) 479 } 480 } 481 countConfig, err := NewRawConfig(map[string]interface{}{ 482 "count": count, 483 }) 484 if err != nil { 485 return nil, err 486 } 487 countConfig.Key = "count" 488 489 // If we have depends fields, then add those in 490 var dependsOn []string 491 if o := listVal.Filter("depends_on"); len(o.Items) > 0 { 492 err := hcl.DecodeObject(&dependsOn, o.Items[0].Val) 493 if err != nil { 494 return nil, fmt.Errorf( 495 "Error reading depends_on for %s[%s]: %s", 496 t, 497 k, 498 err) 499 } 500 } 501 502 // If we have a provider, then parse it out 503 var provider string 504 if o := listVal.Filter("provider"); len(o.Items) > 0 { 505 err := hcl.DecodeObject(&provider, o.Items[0].Val) 506 if err != nil { 507 return nil, fmt.Errorf( 508 "Error reading provider for %s[%s]: %s", 509 t, 510 k, 511 err) 512 } 513 } 514 515 result = append(result, &Resource{ 516 Mode: DataResourceMode, 517 Name: k, 518 Type: t, 519 RawCount: countConfig, 520 RawConfig: rawConfig, 521 Provider: provider, 522 Provisioners: []*Provisioner{}, 523 DependsOn: dependsOn, 524 Lifecycle: ResourceLifecycle{}, 525 }) 526 } 527 528 return result, nil 529 } 530 531 // Given a handle to a HCL object, this recurses into the structure 532 // and pulls out a list of managed resources. 533 // 534 // The resulting resources may not be unique, but each resource 535 // represents exactly one "resource" block in the HCL configuration. 536 // We leave it up to another pass to merge them together. 537 func loadManagedResourcesHcl(list *ast.ObjectList) ([]*Resource, error) { 538 list = list.Children() 539 if len(list.Items) == 0 { 540 return nil, nil 541 } 542 543 // Where all the results will go 544 var result []*Resource 545 546 // Now go over all the types and their children in order to get 547 // all of the actual resources. 548 for _, item := range list.Items { 549 // GH-4385: We detect a pure provisioner resource and give the user 550 // an error about how to do it cleanly. 551 if len(item.Keys) == 4 && item.Keys[2].Token.Value().(string) == "provisioner" { 552 return nil, fmt.Errorf( 553 "position %s: provisioners in a resource should be wrapped in a list\n\n"+ 554 "Example: \"provisioner\": [ { \"local-exec\": ... } ]", 555 item.Pos()) 556 } 557 558 if len(item.Keys) != 2 { 559 return nil, fmt.Errorf( 560 "position %s: resource must be followed by exactly two strings, a type and a name", 561 item.Pos()) 562 } 563 564 t := item.Keys[0].Token.Value().(string) 565 k := item.Keys[1].Token.Value().(string) 566 567 var listVal *ast.ObjectList 568 if ot, ok := item.Val.(*ast.ObjectType); ok { 569 listVal = ot.List 570 } else { 571 return nil, fmt.Errorf("resources %s[%s]: should be an object", t, k) 572 } 573 574 var config map[string]interface{} 575 if err := hcl.DecodeObject(&config, item.Val); err != nil { 576 return nil, fmt.Errorf( 577 "Error reading config for %s[%s]: %s", 578 t, 579 k, 580 err) 581 } 582 583 // Remove the fields we handle specially 584 delete(config, "connection") 585 delete(config, "count") 586 delete(config, "depends_on") 587 delete(config, "provisioner") 588 delete(config, "provider") 589 delete(config, "lifecycle") 590 591 rawConfig, err := NewRawConfig(config) 592 if err != nil { 593 return nil, fmt.Errorf( 594 "Error reading config for %s[%s]: %s", 595 t, 596 k, 597 err) 598 } 599 600 // If we have a count, then figure it out 601 var count string = "1" 602 if o := listVal.Filter("count"); len(o.Items) > 0 { 603 err = hcl.DecodeObject(&count, o.Items[0].Val) 604 if err != nil { 605 return nil, fmt.Errorf( 606 "Error parsing count for %s[%s]: %s", 607 t, 608 k, 609 err) 610 } 611 } 612 countConfig, err := NewRawConfig(map[string]interface{}{ 613 "count": count, 614 }) 615 if err != nil { 616 return nil, err 617 } 618 countConfig.Key = "count" 619 620 // If we have depends fields, then add those in 621 var dependsOn []string 622 if o := listVal.Filter("depends_on"); len(o.Items) > 0 { 623 err := hcl.DecodeObject(&dependsOn, o.Items[0].Val) 624 if err != nil { 625 return nil, fmt.Errorf( 626 "Error reading depends_on for %s[%s]: %s", 627 t, 628 k, 629 err) 630 } 631 } 632 633 // If we have connection info, then parse those out 634 var connInfo map[string]interface{} 635 if o := listVal.Filter("connection"); len(o.Items) > 0 { 636 err := hcl.DecodeObject(&connInfo, o.Items[0].Val) 637 if err != nil { 638 return nil, fmt.Errorf( 639 "Error reading connection info for %s[%s]: %s", 640 t, 641 k, 642 err) 643 } 644 } 645 646 // If we have provisioners, then parse those out 647 var provisioners []*Provisioner 648 if os := listVal.Filter("provisioner"); len(os.Items) > 0 { 649 var err error 650 provisioners, err = loadProvisionersHcl(os, connInfo) 651 if err != nil { 652 return nil, fmt.Errorf( 653 "Error reading provisioners for %s[%s]: %s", 654 t, 655 k, 656 err) 657 } 658 } 659 660 // If we have a provider, then parse it out 661 var provider string 662 if o := listVal.Filter("provider"); len(o.Items) > 0 { 663 err := hcl.DecodeObject(&provider, o.Items[0].Val) 664 if err != nil { 665 return nil, fmt.Errorf( 666 "Error reading provider for %s[%s]: %s", 667 t, 668 k, 669 err) 670 } 671 } 672 673 // Check if the resource should be re-created before 674 // destroying the existing instance 675 var lifecycle ResourceLifecycle 676 if o := listVal.Filter("lifecycle"); len(o.Items) > 0 { 677 // Check for invalid keys 678 valid := []string{"create_before_destroy", "ignore_changes", "prevent_destroy"} 679 if err := checkHCLKeys(o.Items[0].Val, valid); err != nil { 680 return nil, multierror.Prefix(err, fmt.Sprintf( 681 "%s[%s]:", t, k)) 682 } 683 684 var raw map[string]interface{} 685 if err = hcl.DecodeObject(&raw, o.Items[0].Val); err != nil { 686 return nil, fmt.Errorf( 687 "Error parsing lifecycle for %s[%s]: %s", 688 t, 689 k, 690 err) 691 } 692 693 if err := mapstructure.WeakDecode(raw, &lifecycle); err != nil { 694 return nil, fmt.Errorf( 695 "Error parsing lifecycle for %s[%s]: %s", 696 t, 697 k, 698 err) 699 } 700 } 701 702 result = append(result, &Resource{ 703 Mode: ManagedResourceMode, 704 Name: k, 705 Type: t, 706 RawCount: countConfig, 707 RawConfig: rawConfig, 708 Provisioners: provisioners, 709 Provider: provider, 710 DependsOn: dependsOn, 711 Lifecycle: lifecycle, 712 }) 713 } 714 715 return result, nil 716 } 717 718 func loadProvisionersHcl(list *ast.ObjectList, connInfo map[string]interface{}) ([]*Provisioner, error) { 719 list = list.Children() 720 if len(list.Items) == 0 { 721 return nil, nil 722 } 723 724 // Go through each object and turn it into an actual result. 725 result := make([]*Provisioner, 0, len(list.Items)) 726 for _, item := range list.Items { 727 n := item.Keys[0].Token.Value().(string) 728 729 var listVal *ast.ObjectList 730 if ot, ok := item.Val.(*ast.ObjectType); ok { 731 listVal = ot.List 732 } else { 733 return nil, fmt.Errorf("provisioner '%s': should be an object", n) 734 } 735 736 var config map[string]interface{} 737 if err := hcl.DecodeObject(&config, item.Val); err != nil { 738 return nil, err 739 } 740 741 // Delete the "connection" section, handle separately 742 delete(config, "connection") 743 744 rawConfig, err := NewRawConfig(config) 745 if err != nil { 746 return nil, err 747 } 748 749 // Check if we have a provisioner-level connection 750 // block that overrides the resource-level 751 var subConnInfo map[string]interface{} 752 if o := listVal.Filter("connection"); len(o.Items) > 0 { 753 err := hcl.DecodeObject(&subConnInfo, o.Items[0].Val) 754 if err != nil { 755 return nil, err 756 } 757 } 758 759 // Inherit from the resource connInfo any keys 760 // that are not explicitly overriden. 761 if connInfo != nil && subConnInfo != nil { 762 for k, v := range connInfo { 763 if _, ok := subConnInfo[k]; !ok { 764 subConnInfo[k] = v 765 } 766 } 767 } else if subConnInfo == nil { 768 subConnInfo = connInfo 769 } 770 771 // Parse the connInfo 772 connRaw, err := NewRawConfig(subConnInfo) 773 if err != nil { 774 return nil, err 775 } 776 777 result = append(result, &Provisioner{ 778 Type: n, 779 RawConfig: rawConfig, 780 ConnInfo: connRaw, 781 }) 782 } 783 784 return result, nil 785 } 786 787 /* 788 func hclObjectMap(os *hclobj.Object) map[string]ast.ListNode { 789 objects := make(map[string][]*hclobj.Object) 790 791 for _, o := range os.Elem(false) { 792 for _, elem := range o.Elem(true) { 793 val, ok := objects[elem.Key] 794 if !ok { 795 val = make([]*hclobj.Object, 0, 1) 796 } 797 798 val = append(val, elem) 799 objects[elem.Key] = val 800 } 801 } 802 803 return objects 804 } 805 */ 806 807 func checkHCLKeys(node ast.Node, valid []string) error { 808 var list *ast.ObjectList 809 switch n := node.(type) { 810 case *ast.ObjectList: 811 list = n 812 case *ast.ObjectType: 813 list = n.List 814 default: 815 return fmt.Errorf("cannot check HCL keys of type %T", n) 816 } 817 818 validMap := make(map[string]struct{}, len(valid)) 819 for _, v := range valid { 820 validMap[v] = struct{}{} 821 } 822 823 var result error 824 for _, item := range list.Items { 825 key := item.Keys[0].Token.Value().(string) 826 if _, ok := validMap[key]; !ok { 827 result = multierror.Append(result, fmt.Errorf( 828 "invalid key: %s", key)) 829 } 830 } 831 832 return result 833 }