github.com/jrperritt/terraform@v0.1.1-0.20170525065507-96f391dafc38/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 "terraform": struct{}{}, 29 "variable": struct{}{}, 30 } 31 32 // Top-level item should be the object list 33 list, ok := t.Root.Node.(*ast.ObjectList) 34 if !ok { 35 return nil, fmt.Errorf("error parsing: file doesn't contain a root object") 36 } 37 38 // Start building up the actual configuration. 39 config := new(Config) 40 41 // Terraform config 42 if o := list.Filter("terraform"); len(o.Items) > 0 { 43 var err error 44 config.Terraform, err = loadTerraformHcl(o) 45 if err != nil { 46 return nil, err 47 } 48 } 49 50 // Build the variables 51 if vars := list.Filter("variable"); len(vars.Items) > 0 { 52 var err error 53 config.Variables, err = loadVariablesHcl(vars) 54 if err != nil { 55 return nil, err 56 } 57 } 58 59 // Get Atlas configuration 60 if atlas := list.Filter("atlas"); len(atlas.Items) > 0 { 61 var err error 62 config.Atlas, err = loadAtlasHcl(atlas) 63 if err != nil { 64 return nil, err 65 } 66 } 67 68 // Build the modules 69 if modules := list.Filter("module"); len(modules.Items) > 0 { 70 var err error 71 config.Modules, err = loadModulesHcl(modules) 72 if err != nil { 73 return nil, err 74 } 75 } 76 77 // Build the provider configs 78 if providers := list.Filter("provider"); len(providers.Items) > 0 { 79 var err error 80 config.ProviderConfigs, err = loadProvidersHcl(providers) 81 if err != nil { 82 return nil, err 83 } 84 } 85 86 // Build the resources 87 { 88 var err error 89 managedResourceConfigs := list.Filter("resource") 90 dataResourceConfigs := list.Filter("data") 91 92 config.Resources = make( 93 []*Resource, 0, 94 len(managedResourceConfigs.Items)+len(dataResourceConfigs.Items), 95 ) 96 97 managedResources, err := loadManagedResourcesHcl(managedResourceConfigs) 98 if err != nil { 99 return nil, err 100 } 101 dataResources, err := loadDataResourcesHcl(dataResourceConfigs) 102 if err != nil { 103 return nil, err 104 } 105 106 config.Resources = append(config.Resources, dataResources...) 107 config.Resources = append(config.Resources, managedResources...) 108 } 109 110 // Build the outputs 111 if outputs := list.Filter("output"); len(outputs.Items) > 0 { 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 _, item := range list.Items { 121 if len(item.Keys) == 0 { 122 // Not sure how this would happen, but let's avoid a panic 123 continue 124 } 125 126 k := item.Keys[0].Token.Value().(string) 127 if _, ok := validKeys[k]; ok { 128 continue 129 } 130 131 config.unknownKeys = append(config.unknownKeys, k) 132 } 133 134 return config, nil 135 } 136 137 // loadFileHcl is a fileLoaderFunc that knows how to read HCL 138 // files and turn them into hclConfigurables. 139 func loadFileHcl(root string) (configurable, []string, error) { 140 // Read the HCL file and prepare for parsing 141 d, err := ioutil.ReadFile(root) 142 if err != nil { 143 return nil, nil, fmt.Errorf( 144 "Error reading %s: %s", root, err) 145 } 146 147 // Parse it 148 hclRoot, err := hcl.Parse(string(d)) 149 if err != nil { 150 return nil, nil, fmt.Errorf( 151 "Error parsing %s: %s", root, err) 152 } 153 154 // Start building the result 155 result := &hclConfigurable{ 156 File: root, 157 Root: hclRoot, 158 } 159 160 // Dive in, find the imports. This is disabled for now since 161 // imports were removed prior to Terraform 0.1. The code is 162 // remaining here commented for historical purposes. 163 /* 164 imports := obj.Get("import") 165 if imports == nil { 166 result.Object.Ref() 167 return result, nil, nil 168 } 169 170 if imports.Type() != libucl.ObjectTypeString { 171 imports.Close() 172 173 return nil, nil, fmt.Errorf( 174 "Error in %s: all 'import' declarations should be in the format\n"+ 175 "`import \"foo\"` (Got type %s)", 176 root, 177 imports.Type()) 178 } 179 180 // Gather all the import paths 181 importPaths := make([]string, 0, imports.Len()) 182 iter := imports.Iterate(false) 183 for imp := iter.Next(); imp != nil; imp = iter.Next() { 184 path := imp.ToString() 185 if !filepath.IsAbs(path) { 186 // Relative paths are relative to the Terraform file itself 187 dir := filepath.Dir(root) 188 path = filepath.Join(dir, path) 189 } 190 191 importPaths = append(importPaths, path) 192 imp.Close() 193 } 194 iter.Close() 195 imports.Close() 196 197 result.Object.Ref() 198 */ 199 200 return result, nil, nil 201 } 202 203 // Given a handle to a HCL object, this transforms it into the Terraform config 204 func loadTerraformHcl(list *ast.ObjectList) (*Terraform, error) { 205 if len(list.Items) > 1 { 206 return nil, fmt.Errorf("only one 'terraform' block allowed per module") 207 } 208 209 // Get our one item 210 item := list.Items[0] 211 212 // This block should have an empty top level ObjectItem. If there are keys 213 // here, it's likely because we have a flattened JSON object, and we can 214 // lift this into a nested ObjectList to decode properly. 215 if len(item.Keys) > 0 { 216 item = &ast.ObjectItem{ 217 Val: &ast.ObjectType{ 218 List: &ast.ObjectList{ 219 Items: []*ast.ObjectItem{item}, 220 }, 221 }, 222 } 223 } 224 225 // We need the item value as an ObjectList 226 var listVal *ast.ObjectList 227 if ot, ok := item.Val.(*ast.ObjectType); ok { 228 listVal = ot.List 229 } else { 230 return nil, fmt.Errorf("terraform block: should be an object") 231 } 232 233 // NOTE: We purposely don't validate unknown HCL keys here so that 234 // we can potentially read _future_ Terraform version config (to 235 // still be able to validate the required version). 236 // 237 // We should still keep track of unknown keys to validate later, but 238 // HCL doesn't currently support that. 239 240 var config Terraform 241 if err := hcl.DecodeObject(&config, item.Val); err != nil { 242 return nil, fmt.Errorf( 243 "Error reading terraform config: %s", 244 err) 245 } 246 247 // If we have provisioners, then parse those out 248 if os := listVal.Filter("backend"); len(os.Items) > 0 { 249 var err error 250 config.Backend, err = loadTerraformBackendHcl(os) 251 if err != nil { 252 return nil, fmt.Errorf( 253 "Error reading backend config for terraform block: %s", 254 err) 255 } 256 } 257 258 return &config, nil 259 } 260 261 // Loads the Backend configuration from an object list. 262 func loadTerraformBackendHcl(list *ast.ObjectList) (*Backend, error) { 263 if len(list.Items) > 1 { 264 return nil, fmt.Errorf("only one 'backend' block allowed") 265 } 266 267 // Get our one item 268 item := list.Items[0] 269 270 // Verify the keys 271 if len(item.Keys) != 1 { 272 return nil, fmt.Errorf( 273 "position %s: 'backend' must be followed by exactly one string: a type", 274 item.Pos()) 275 } 276 277 typ := item.Keys[0].Token.Value().(string) 278 279 // Decode the raw config 280 var config map[string]interface{} 281 if err := hcl.DecodeObject(&config, item.Val); err != nil { 282 return nil, fmt.Errorf( 283 "Error reading backend config: %s", 284 err) 285 } 286 287 rawConfig, err := NewRawConfig(config) 288 if err != nil { 289 return nil, fmt.Errorf( 290 "Error reading backend config: %s", 291 err) 292 } 293 294 b := &Backend{ 295 Type: typ, 296 RawConfig: rawConfig, 297 } 298 b.Hash = b.Rehash() 299 300 return b, nil 301 } 302 303 // Given a handle to a HCL object, this transforms it into the Atlas 304 // configuration. 305 func loadAtlasHcl(list *ast.ObjectList) (*AtlasConfig, error) { 306 if len(list.Items) > 1 { 307 return nil, fmt.Errorf("only one 'atlas' block allowed") 308 } 309 310 // Get our one item 311 item := list.Items[0] 312 313 var config AtlasConfig 314 if err := hcl.DecodeObject(&config, item.Val); err != nil { 315 return nil, fmt.Errorf( 316 "Error reading atlas config: %s", 317 err) 318 } 319 320 return &config, nil 321 } 322 323 // Given a handle to a HCL object, this recurses into the structure 324 // and pulls out a list of modules. 325 // 326 // The resulting modules may not be unique, but each module 327 // represents exactly one module definition in the HCL configuration. 328 // We leave it up to another pass to merge them together. 329 func loadModulesHcl(list *ast.ObjectList) ([]*Module, error) { 330 if err := assertAllBlocksHaveNames("module", list); err != nil { 331 return nil, err 332 } 333 334 list = list.Children() 335 if len(list.Items) == 0 { 336 return nil, nil 337 } 338 339 // Where all the results will go 340 var result []*Module 341 342 // Now go over all the types and their children in order to get 343 // all of the actual resources. 344 for _, item := range list.Items { 345 k := item.Keys[0].Token.Value().(string) 346 347 var listVal *ast.ObjectList 348 if ot, ok := item.Val.(*ast.ObjectType); ok { 349 listVal = ot.List 350 } else { 351 return nil, fmt.Errorf("module '%s': should be an object", k) 352 } 353 354 var config map[string]interface{} 355 if err := hcl.DecodeObject(&config, item.Val); err != nil { 356 return nil, fmt.Errorf( 357 "Error reading config for %s: %s", 358 k, 359 err) 360 } 361 362 // Remove the fields we handle specially 363 delete(config, "source") 364 365 rawConfig, err := NewRawConfig(config) 366 if err != nil { 367 return nil, fmt.Errorf( 368 "Error reading config for %s: %s", 369 k, 370 err) 371 } 372 373 // If we have a count, then figure it out 374 var source string 375 if o := listVal.Filter("source"); len(o.Items) > 0 { 376 err = hcl.DecodeObject(&source, o.Items[0].Val) 377 if err != nil { 378 return nil, fmt.Errorf( 379 "Error parsing source for %s: %s", 380 k, 381 err) 382 } 383 } 384 385 result = append(result, &Module{ 386 Name: k, 387 Source: source, 388 RawConfig: rawConfig, 389 }) 390 } 391 392 return result, nil 393 } 394 395 // LoadOutputsHcl recurses into the given HCL object and turns 396 // it into a mapping of outputs. 397 func loadOutputsHcl(list *ast.ObjectList) ([]*Output, error) { 398 if err := assertAllBlocksHaveNames("output", list); err != nil { 399 return nil, err 400 } 401 402 list = list.Children() 403 404 // Go through each object and turn it into an actual result. 405 result := make([]*Output, 0, len(list.Items)) 406 for _, item := range list.Items { 407 n := item.Keys[0].Token.Value().(string) 408 409 var listVal *ast.ObjectList 410 if ot, ok := item.Val.(*ast.ObjectType); ok { 411 listVal = ot.List 412 } else { 413 return nil, fmt.Errorf("output '%s': should be an object", n) 414 } 415 416 var config map[string]interface{} 417 if err := hcl.DecodeObject(&config, item.Val); err != nil { 418 return nil, err 419 } 420 421 // Delete special keys 422 delete(config, "depends_on") 423 424 rawConfig, err := NewRawConfig(config) 425 if err != nil { 426 return nil, fmt.Errorf( 427 "Error reading config for output %s: %s", 428 n, 429 err) 430 } 431 432 // If we have depends fields, then add those in 433 var dependsOn []string 434 if o := listVal.Filter("depends_on"); len(o.Items) > 0 { 435 err := hcl.DecodeObject(&dependsOn, o.Items[0].Val) 436 if err != nil { 437 return nil, fmt.Errorf( 438 "Error reading depends_on for output %q: %s", 439 n, 440 err) 441 } 442 } 443 444 result = append(result, &Output{ 445 Name: n, 446 RawConfig: rawConfig, 447 DependsOn: dependsOn, 448 }) 449 } 450 451 return result, nil 452 } 453 454 // LoadVariablesHcl recurses into the given HCL object and turns 455 // it into a list of variables. 456 func loadVariablesHcl(list *ast.ObjectList) ([]*Variable, error) { 457 if err := assertAllBlocksHaveNames("variable", list); err != nil { 458 return nil, err 459 } 460 461 list = list.Children() 462 463 // hclVariable is the structure each variable is decoded into 464 type hclVariable struct { 465 DeclaredType string `hcl:"type"` 466 Default interface{} 467 Description string 468 Fields []string `hcl:",decodedFields"` 469 } 470 471 // Go through each object and turn it into an actual result. 472 result := make([]*Variable, 0, len(list.Items)) 473 for _, item := range list.Items { 474 // Clean up items from JSON 475 unwrapHCLObjectKeysFromJSON(item, 1) 476 477 // Verify the keys 478 if len(item.Keys) != 1 { 479 return nil, fmt.Errorf( 480 "position %s: 'variable' must be followed by exactly one strings: a name", 481 item.Pos()) 482 } 483 484 n := item.Keys[0].Token.Value().(string) 485 if !NameRegexp.MatchString(n) { 486 return nil, fmt.Errorf( 487 "position %s: 'variable' name must match regular expression: %s", 488 item.Pos(), NameRegexp) 489 } 490 491 // Check for invalid keys 492 valid := []string{"type", "default", "description"} 493 if err := checkHCLKeys(item.Val, valid); err != nil { 494 return nil, multierror.Prefix(err, fmt.Sprintf( 495 "variable[%s]:", n)) 496 } 497 498 // Decode into hclVariable to get typed values 499 var hclVar hclVariable 500 if err := hcl.DecodeObject(&hclVar, item.Val); err != nil { 501 return nil, err 502 } 503 504 // Defaults turn into a slice of map[string]interface{} and 505 // we need to make sure to convert that down into the 506 // proper type for Config. 507 if ms, ok := hclVar.Default.([]map[string]interface{}); ok { 508 def := make(map[string]interface{}) 509 for _, m := range ms { 510 for k, v := range m { 511 def[k] = v 512 } 513 } 514 515 hclVar.Default = def 516 } 517 518 // Build the new variable and do some basic validation 519 newVar := &Variable{ 520 Name: n, 521 DeclaredType: hclVar.DeclaredType, 522 Default: hclVar.Default, 523 Description: hclVar.Description, 524 } 525 if err := newVar.ValidateTypeAndDefault(); err != nil { 526 return nil, err 527 } 528 529 result = append(result, newVar) 530 } 531 532 return result, nil 533 } 534 535 // LoadProvidersHcl recurses into the given HCL object and turns 536 // it into a mapping of provider configs. 537 func loadProvidersHcl(list *ast.ObjectList) ([]*ProviderConfig, error) { 538 if err := assertAllBlocksHaveNames("provider", list); err != nil { 539 return nil, err 540 } 541 542 list = list.Children() 543 if len(list.Items) == 0 { 544 return nil, nil 545 } 546 547 // Go through each object and turn it into an actual result. 548 result := make([]*ProviderConfig, 0, len(list.Items)) 549 for _, item := range list.Items { 550 n := item.Keys[0].Token.Value().(string) 551 552 var listVal *ast.ObjectList 553 if ot, ok := item.Val.(*ast.ObjectType); ok { 554 listVal = ot.List 555 } else { 556 return nil, fmt.Errorf("module '%s': should be an object", n) 557 } 558 559 var config map[string]interface{} 560 if err := hcl.DecodeObject(&config, item.Val); err != nil { 561 return nil, err 562 } 563 564 delete(config, "alias") 565 566 rawConfig, err := NewRawConfig(config) 567 if err != nil { 568 return nil, fmt.Errorf( 569 "Error reading config for provider config %s: %s", 570 n, 571 err) 572 } 573 574 // If we have an alias field, then add those in 575 var alias string 576 if a := listVal.Filter("alias"); len(a.Items) > 0 { 577 err := hcl.DecodeObject(&alias, a.Items[0].Val) 578 if err != nil { 579 return nil, fmt.Errorf( 580 "Error reading alias for provider[%s]: %s", 581 n, 582 err) 583 } 584 } 585 586 result = append(result, &ProviderConfig{ 587 Name: n, 588 Alias: alias, 589 RawConfig: rawConfig, 590 }) 591 } 592 593 return result, nil 594 } 595 596 // Given a handle to a HCL object, this recurses into the structure 597 // and pulls out a list of data sources. 598 // 599 // The resulting data sources may not be unique, but each one 600 // represents exactly one data definition in the HCL configuration. 601 // We leave it up to another pass to merge them together. 602 func loadDataResourcesHcl(list *ast.ObjectList) ([]*Resource, error) { 603 if err := assertAllBlocksHaveNames("data", list); err != nil { 604 return nil, err 605 } 606 607 list = list.Children() 608 if len(list.Items) == 0 { 609 return nil, nil 610 } 611 612 // Where all the results will go 613 var result []*Resource 614 615 // Now go over all the types and their children in order to get 616 // all of the actual resources. 617 for _, item := range list.Items { 618 if len(item.Keys) != 2 { 619 return nil, fmt.Errorf( 620 "position %s: 'data' must be followed by exactly two strings: a type and a name", 621 item.Pos()) 622 } 623 624 t := item.Keys[0].Token.Value().(string) 625 k := item.Keys[1].Token.Value().(string) 626 627 var listVal *ast.ObjectList 628 if ot, ok := item.Val.(*ast.ObjectType); ok { 629 listVal = ot.List 630 } else { 631 return nil, fmt.Errorf("data sources %s[%s]: should be an object", t, k) 632 } 633 634 var config map[string]interface{} 635 if err := hcl.DecodeObject(&config, item.Val); err != nil { 636 return nil, fmt.Errorf( 637 "Error reading config for %s[%s]: %s", 638 t, 639 k, 640 err) 641 } 642 643 // Remove the fields we handle specially 644 delete(config, "depends_on") 645 delete(config, "provider") 646 delete(config, "count") 647 648 rawConfig, err := NewRawConfig(config) 649 if err != nil { 650 return nil, fmt.Errorf( 651 "Error reading config for %s[%s]: %s", 652 t, 653 k, 654 err) 655 } 656 657 // If we have a count, then figure it out 658 var count string = "1" 659 if o := listVal.Filter("count"); len(o.Items) > 0 { 660 err = hcl.DecodeObject(&count, o.Items[0].Val) 661 if err != nil { 662 return nil, fmt.Errorf( 663 "Error parsing count for %s[%s]: %s", 664 t, 665 k, 666 err) 667 } 668 } 669 countConfig, err := NewRawConfig(map[string]interface{}{ 670 "count": count, 671 }) 672 if err != nil { 673 return nil, err 674 } 675 countConfig.Key = "count" 676 677 // If we have depends fields, then add those in 678 var dependsOn []string 679 if o := listVal.Filter("depends_on"); len(o.Items) > 0 { 680 err := hcl.DecodeObject(&dependsOn, o.Items[0].Val) 681 if err != nil { 682 return nil, fmt.Errorf( 683 "Error reading depends_on for %s[%s]: %s", 684 t, 685 k, 686 err) 687 } 688 } 689 690 // If we have a provider, then parse it out 691 var provider string 692 if o := listVal.Filter("provider"); len(o.Items) > 0 { 693 err := hcl.DecodeObject(&provider, o.Items[0].Val) 694 if err != nil { 695 return nil, fmt.Errorf( 696 "Error reading provider for %s[%s]: %s", 697 t, 698 k, 699 err) 700 } 701 } 702 703 result = append(result, &Resource{ 704 Mode: DataResourceMode, 705 Name: k, 706 Type: t, 707 RawCount: countConfig, 708 RawConfig: rawConfig, 709 Provider: provider, 710 Provisioners: []*Provisioner{}, 711 DependsOn: dependsOn, 712 Lifecycle: ResourceLifecycle{}, 713 }) 714 } 715 716 return result, nil 717 } 718 719 // Given a handle to a HCL object, this recurses into the structure 720 // and pulls out a list of managed resources. 721 // 722 // The resulting resources may not be unique, but each resource 723 // represents exactly one "resource" block in the HCL configuration. 724 // We leave it up to another pass to merge them together. 725 func loadManagedResourcesHcl(list *ast.ObjectList) ([]*Resource, error) { 726 list = list.Children() 727 if len(list.Items) == 0 { 728 return nil, nil 729 } 730 731 // Where all the results will go 732 var result []*Resource 733 734 // Now go over all the types and their children in order to get 735 // all of the actual resources. 736 for _, item := range list.Items { 737 // GH-4385: We detect a pure provisioner resource and give the user 738 // an error about how to do it cleanly. 739 if len(item.Keys) == 4 && item.Keys[2].Token.Value().(string) == "provisioner" { 740 return nil, fmt.Errorf( 741 "position %s: provisioners in a resource should be wrapped in a list\n\n"+ 742 "Example: \"provisioner\": [ { \"local-exec\": ... } ]", 743 item.Pos()) 744 } 745 746 // Fix up JSON input 747 unwrapHCLObjectKeysFromJSON(item, 2) 748 749 if len(item.Keys) != 2 { 750 return nil, fmt.Errorf( 751 "position %s: resource must be followed by exactly two strings, a type and a name", 752 item.Pos()) 753 } 754 755 t := item.Keys[0].Token.Value().(string) 756 k := item.Keys[1].Token.Value().(string) 757 758 var listVal *ast.ObjectList 759 if ot, ok := item.Val.(*ast.ObjectType); ok { 760 listVal = ot.List 761 } else { 762 return nil, fmt.Errorf("resources %s[%s]: should be an object", t, k) 763 } 764 765 var config map[string]interface{} 766 if err := hcl.DecodeObject(&config, item.Val); err != nil { 767 return nil, fmt.Errorf( 768 "Error reading config for %s[%s]: %s", 769 t, 770 k, 771 err) 772 } 773 774 // Remove the fields we handle specially 775 delete(config, "connection") 776 delete(config, "count") 777 delete(config, "depends_on") 778 delete(config, "provisioner") 779 delete(config, "provider") 780 delete(config, "lifecycle") 781 782 rawConfig, err := NewRawConfig(config) 783 if err != nil { 784 return nil, fmt.Errorf( 785 "Error reading config for %s[%s]: %s", 786 t, 787 k, 788 err) 789 } 790 791 // If we have a count, then figure it out 792 var count string = "1" 793 if o := listVal.Filter("count"); len(o.Items) > 0 { 794 err = hcl.DecodeObject(&count, o.Items[0].Val) 795 if err != nil { 796 return nil, fmt.Errorf( 797 "Error parsing count for %s[%s]: %s", 798 t, 799 k, 800 err) 801 } 802 } 803 countConfig, err := NewRawConfig(map[string]interface{}{ 804 "count": count, 805 }) 806 if err != nil { 807 return nil, err 808 } 809 countConfig.Key = "count" 810 811 // If we have depends fields, then add those in 812 var dependsOn []string 813 if o := listVal.Filter("depends_on"); len(o.Items) > 0 { 814 err := hcl.DecodeObject(&dependsOn, o.Items[0].Val) 815 if err != nil { 816 return nil, fmt.Errorf( 817 "Error reading depends_on for %s[%s]: %s", 818 t, 819 k, 820 err) 821 } 822 } 823 824 // If we have connection info, then parse those out 825 var connInfo map[string]interface{} 826 if o := listVal.Filter("connection"); len(o.Items) > 0 { 827 err := hcl.DecodeObject(&connInfo, o.Items[0].Val) 828 if err != nil { 829 return nil, fmt.Errorf( 830 "Error reading connection info for %s[%s]: %s", 831 t, 832 k, 833 err) 834 } 835 } 836 837 // If we have provisioners, then parse those out 838 var provisioners []*Provisioner 839 if os := listVal.Filter("provisioner"); len(os.Items) > 0 { 840 var err error 841 provisioners, err = loadProvisionersHcl(os, connInfo) 842 if err != nil { 843 return nil, fmt.Errorf( 844 "Error reading provisioners for %s[%s]: %s", 845 t, 846 k, 847 err) 848 } 849 } 850 851 // If we have a provider, then parse it out 852 var provider string 853 if o := listVal.Filter("provider"); len(o.Items) > 0 { 854 err := hcl.DecodeObject(&provider, o.Items[0].Val) 855 if err != nil { 856 return nil, fmt.Errorf( 857 "Error reading provider for %s[%s]: %s", 858 t, 859 k, 860 err) 861 } 862 } 863 864 // Check if the resource should be re-created before 865 // destroying the existing instance 866 var lifecycle ResourceLifecycle 867 if o := listVal.Filter("lifecycle"); len(o.Items) > 0 { 868 if len(o.Items) > 1 { 869 return nil, fmt.Errorf( 870 "%s[%s]: Multiple lifecycle blocks found, expected one", 871 t, k) 872 } 873 874 // Check for invalid keys 875 valid := []string{"create_before_destroy", "ignore_changes", "prevent_destroy"} 876 if err := checkHCLKeys(o.Items[0].Val, valid); err != nil { 877 return nil, multierror.Prefix(err, fmt.Sprintf( 878 "%s[%s]:", t, k)) 879 } 880 881 var raw map[string]interface{} 882 if err = hcl.DecodeObject(&raw, o.Items[0].Val); err != nil { 883 return nil, fmt.Errorf( 884 "Error parsing lifecycle for %s[%s]: %s", 885 t, 886 k, 887 err) 888 } 889 890 if err := mapstructure.WeakDecode(raw, &lifecycle); err != nil { 891 return nil, fmt.Errorf( 892 "Error parsing lifecycle for %s[%s]: %s", 893 t, 894 k, 895 err) 896 } 897 } 898 899 result = append(result, &Resource{ 900 Mode: ManagedResourceMode, 901 Name: k, 902 Type: t, 903 RawCount: countConfig, 904 RawConfig: rawConfig, 905 Provisioners: provisioners, 906 Provider: provider, 907 DependsOn: dependsOn, 908 Lifecycle: lifecycle, 909 }) 910 } 911 912 return result, nil 913 } 914 915 func loadProvisionersHcl(list *ast.ObjectList, connInfo map[string]interface{}) ([]*Provisioner, error) { 916 if err := assertAllBlocksHaveNames("provisioner", list); err != nil { 917 return nil, err 918 } 919 920 list = list.Children() 921 if len(list.Items) == 0 { 922 return nil, nil 923 } 924 925 // Go through each object and turn it into an actual result. 926 result := make([]*Provisioner, 0, len(list.Items)) 927 for _, item := range list.Items { 928 n := item.Keys[0].Token.Value().(string) 929 930 var listVal *ast.ObjectList 931 if ot, ok := item.Val.(*ast.ObjectType); ok { 932 listVal = ot.List 933 } else { 934 return nil, fmt.Errorf("provisioner '%s': should be an object", n) 935 } 936 937 var config map[string]interface{} 938 if err := hcl.DecodeObject(&config, item.Val); err != nil { 939 return nil, err 940 } 941 942 // Parse the "when" value 943 when := ProvisionerWhenCreate 944 if v, ok := config["when"]; ok { 945 switch v { 946 case "create": 947 when = ProvisionerWhenCreate 948 case "destroy": 949 when = ProvisionerWhenDestroy 950 default: 951 return nil, fmt.Errorf( 952 "position %s: 'provisioner' when must be 'create' or 'destroy'", 953 item.Pos()) 954 } 955 } 956 957 // Parse the "on_failure" value 958 onFailure := ProvisionerOnFailureFail 959 if v, ok := config["on_failure"]; ok { 960 switch v { 961 case "continue": 962 onFailure = ProvisionerOnFailureContinue 963 case "fail": 964 onFailure = ProvisionerOnFailureFail 965 default: 966 return nil, fmt.Errorf( 967 "position %s: 'provisioner' on_failure must be 'continue' or 'fail'", 968 item.Pos()) 969 } 970 } 971 972 // Delete fields we special case 973 delete(config, "connection") 974 delete(config, "when") 975 delete(config, "on_failure") 976 977 rawConfig, err := NewRawConfig(config) 978 if err != nil { 979 return nil, err 980 } 981 982 // Check if we have a provisioner-level connection 983 // block that overrides the resource-level 984 var subConnInfo map[string]interface{} 985 if o := listVal.Filter("connection"); len(o.Items) > 0 { 986 err := hcl.DecodeObject(&subConnInfo, o.Items[0].Val) 987 if err != nil { 988 return nil, err 989 } 990 } 991 992 // Inherit from the resource connInfo any keys 993 // that are not explicitly overriden. 994 if connInfo != nil && subConnInfo != nil { 995 for k, v := range connInfo { 996 if _, ok := subConnInfo[k]; !ok { 997 subConnInfo[k] = v 998 } 999 } 1000 } else if subConnInfo == nil { 1001 subConnInfo = connInfo 1002 } 1003 1004 // Parse the connInfo 1005 connRaw, err := NewRawConfig(subConnInfo) 1006 if err != nil { 1007 return nil, err 1008 } 1009 1010 result = append(result, &Provisioner{ 1011 Type: n, 1012 RawConfig: rawConfig, 1013 ConnInfo: connRaw, 1014 When: when, 1015 OnFailure: onFailure, 1016 }) 1017 } 1018 1019 return result, nil 1020 } 1021 1022 /* 1023 func hclObjectMap(os *hclobj.Object) map[string]ast.ListNode { 1024 objects := make(map[string][]*hclobj.Object) 1025 1026 for _, o := range os.Elem(false) { 1027 for _, elem := range o.Elem(true) { 1028 val, ok := objects[elem.Key] 1029 if !ok { 1030 val = make([]*hclobj.Object, 0, 1) 1031 } 1032 1033 val = append(val, elem) 1034 objects[elem.Key] = val 1035 } 1036 } 1037 1038 return objects 1039 } 1040 */ 1041 1042 // assertAllBlocksHaveNames returns an error if any of the items in 1043 // the given object list are blocks without keys (like "module {}") 1044 // or simple assignments (like "module = 1"). It returns nil if 1045 // neither of these things are true. 1046 // 1047 // The given name is used in any generated error messages, and should 1048 // be the name of the block we're dealing with. The given list should 1049 // be the result of calling .Filter on an object list with that same 1050 // name. 1051 func assertAllBlocksHaveNames(name string, list *ast.ObjectList) error { 1052 if elem := list.Elem(); len(elem.Items) != 0 { 1053 switch et := elem.Items[0].Val.(type) { 1054 case *ast.ObjectType: 1055 pos := et.Lbrace 1056 return fmt.Errorf("%s: %q must be followed by a name", pos, name) 1057 default: 1058 pos := elem.Items[0].Val.Pos() 1059 return fmt.Errorf("%s: %q must be a configuration block", pos, name) 1060 } 1061 } 1062 return nil 1063 } 1064 1065 func checkHCLKeys(node ast.Node, valid []string) error { 1066 var list *ast.ObjectList 1067 switch n := node.(type) { 1068 case *ast.ObjectList: 1069 list = n 1070 case *ast.ObjectType: 1071 list = n.List 1072 default: 1073 return fmt.Errorf("cannot check HCL keys of type %T", n) 1074 } 1075 1076 validMap := make(map[string]struct{}, len(valid)) 1077 for _, v := range valid { 1078 validMap[v] = struct{}{} 1079 } 1080 1081 var result error 1082 for _, item := range list.Items { 1083 key := item.Keys[0].Token.Value().(string) 1084 if _, ok := validMap[key]; !ok { 1085 result = multierror.Append(result, fmt.Errorf( 1086 "invalid key: %s", key)) 1087 } 1088 } 1089 1090 return result 1091 } 1092 1093 // unwrapHCLObjectKeysFromJSON cleans up an edge case that can occur when 1094 // parsing JSON as input: if we're parsing JSON then directly nested 1095 // items will show up as additional "keys". 1096 // 1097 // For objects that expect a fixed number of keys, this breaks the 1098 // decoding process. This function unwraps the object into what it would've 1099 // looked like if it came directly from HCL by specifying the number of keys 1100 // you expect. 1101 // 1102 // Example: 1103 // 1104 // { "foo": { "baz": {} } } 1105 // 1106 // Will show up with Keys being: []string{"foo", "baz"} 1107 // when we really just want the first two. This function will fix this. 1108 func unwrapHCLObjectKeysFromJSON(item *ast.ObjectItem, depth int) { 1109 if len(item.Keys) > depth && item.Keys[0].Token.JSON { 1110 for len(item.Keys) > depth { 1111 // Pop off the last key 1112 n := len(item.Keys) 1113 key := item.Keys[n-1] 1114 item.Keys[n-1] = nil 1115 item.Keys = item.Keys[:n-1] 1116 1117 // Wrap our value in a list 1118 item.Val = &ast.ObjectType{ 1119 List: &ast.ObjectList{ 1120 Items: []*ast.ObjectItem{ 1121 &ast.ObjectItem{ 1122 Keys: []*ast.ObjectKey{key}, 1123 Val: item.Val, 1124 }, 1125 }, 1126 }, 1127 } 1128 } 1129 } 1130 }