github.com/vic3lord/terraform@v0.8.0-rc1.0.20170626102919-16c6dd2cb372/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 delete(config, "version") 566 567 rawConfig, err := NewRawConfig(config) 568 if err != nil { 569 return nil, fmt.Errorf( 570 "Error reading config for provider config %s: %s", 571 n, 572 err) 573 } 574 575 // If we have an alias field, then add those in 576 var alias string 577 if a := listVal.Filter("alias"); len(a.Items) > 0 { 578 err := hcl.DecodeObject(&alias, a.Items[0].Val) 579 if err != nil { 580 return nil, fmt.Errorf( 581 "Error reading alias for provider[%s]: %s", 582 n, 583 err) 584 } 585 } 586 587 // If we have a version field then extract it 588 var version string 589 if a := listVal.Filter("version"); len(a.Items) > 0 { 590 err := hcl.DecodeObject(&version, a.Items[0].Val) 591 if err != nil { 592 return nil, fmt.Errorf( 593 "Error reading version for provider[%s]: %s", 594 n, 595 err) 596 } 597 } 598 599 result = append(result, &ProviderConfig{ 600 Name: n, 601 Alias: alias, 602 Version: version, 603 RawConfig: rawConfig, 604 }) 605 } 606 607 return result, nil 608 } 609 610 // Given a handle to a HCL object, this recurses into the structure 611 // and pulls out a list of data sources. 612 // 613 // The resulting data sources may not be unique, but each one 614 // represents exactly one data definition in the HCL configuration. 615 // We leave it up to another pass to merge them together. 616 func loadDataResourcesHcl(list *ast.ObjectList) ([]*Resource, error) { 617 if err := assertAllBlocksHaveNames("data", list); err != nil { 618 return nil, err 619 } 620 621 list = list.Children() 622 if len(list.Items) == 0 { 623 return nil, nil 624 } 625 626 // Where all the results will go 627 var result []*Resource 628 629 // Now go over all the types and their children in order to get 630 // all of the actual resources. 631 for _, item := range list.Items { 632 if len(item.Keys) != 2 { 633 return nil, fmt.Errorf( 634 "position %s: 'data' must be followed by exactly two strings: a type and a name", 635 item.Pos()) 636 } 637 638 t := item.Keys[0].Token.Value().(string) 639 k := item.Keys[1].Token.Value().(string) 640 641 var listVal *ast.ObjectList 642 if ot, ok := item.Val.(*ast.ObjectType); ok { 643 listVal = ot.List 644 } else { 645 return nil, fmt.Errorf("data sources %s[%s]: should be an object", t, k) 646 } 647 648 var config map[string]interface{} 649 if err := hcl.DecodeObject(&config, item.Val); err != nil { 650 return nil, fmt.Errorf( 651 "Error reading config for %s[%s]: %s", 652 t, 653 k, 654 err) 655 } 656 657 // Remove the fields we handle specially 658 delete(config, "depends_on") 659 delete(config, "provider") 660 delete(config, "count") 661 662 rawConfig, err := NewRawConfig(config) 663 if err != nil { 664 return nil, fmt.Errorf( 665 "Error reading config for %s[%s]: %s", 666 t, 667 k, 668 err) 669 } 670 671 // If we have a count, then figure it out 672 var count string = "1" 673 if o := listVal.Filter("count"); len(o.Items) > 0 { 674 err = hcl.DecodeObject(&count, o.Items[0].Val) 675 if err != nil { 676 return nil, fmt.Errorf( 677 "Error parsing count for %s[%s]: %s", 678 t, 679 k, 680 err) 681 } 682 } 683 countConfig, err := NewRawConfig(map[string]interface{}{ 684 "count": count, 685 }) 686 if err != nil { 687 return nil, err 688 } 689 countConfig.Key = "count" 690 691 // If we have depends fields, then add those in 692 var dependsOn []string 693 if o := listVal.Filter("depends_on"); len(o.Items) > 0 { 694 err := hcl.DecodeObject(&dependsOn, o.Items[0].Val) 695 if err != nil { 696 return nil, fmt.Errorf( 697 "Error reading depends_on for %s[%s]: %s", 698 t, 699 k, 700 err) 701 } 702 } 703 704 // If we have a provider, then parse it out 705 var provider string 706 if o := listVal.Filter("provider"); len(o.Items) > 0 { 707 err := hcl.DecodeObject(&provider, o.Items[0].Val) 708 if err != nil { 709 return nil, fmt.Errorf( 710 "Error reading provider for %s[%s]: %s", 711 t, 712 k, 713 err) 714 } 715 } 716 717 result = append(result, &Resource{ 718 Mode: DataResourceMode, 719 Name: k, 720 Type: t, 721 RawCount: countConfig, 722 RawConfig: rawConfig, 723 Provider: provider, 724 Provisioners: []*Provisioner{}, 725 DependsOn: dependsOn, 726 Lifecycle: ResourceLifecycle{}, 727 }) 728 } 729 730 return result, nil 731 } 732 733 // Given a handle to a HCL object, this recurses into the structure 734 // and pulls out a list of managed resources. 735 // 736 // The resulting resources may not be unique, but each resource 737 // represents exactly one "resource" block in the HCL configuration. 738 // We leave it up to another pass to merge them together. 739 func loadManagedResourcesHcl(list *ast.ObjectList) ([]*Resource, error) { 740 list = list.Children() 741 if len(list.Items) == 0 { 742 return nil, nil 743 } 744 745 // Where all the results will go 746 var result []*Resource 747 748 // Now go over all the types and their children in order to get 749 // all of the actual resources. 750 for _, item := range list.Items { 751 // GH-4385: We detect a pure provisioner resource and give the user 752 // an error about how to do it cleanly. 753 if len(item.Keys) == 4 && item.Keys[2].Token.Value().(string) == "provisioner" { 754 return nil, fmt.Errorf( 755 "position %s: provisioners in a resource should be wrapped in a list\n\n"+ 756 "Example: \"provisioner\": [ { \"local-exec\": ... } ]", 757 item.Pos()) 758 } 759 760 // Fix up JSON input 761 unwrapHCLObjectKeysFromJSON(item, 2) 762 763 if len(item.Keys) != 2 { 764 return nil, fmt.Errorf( 765 "position %s: resource must be followed by exactly two strings, a type and a name", 766 item.Pos()) 767 } 768 769 t := item.Keys[0].Token.Value().(string) 770 k := item.Keys[1].Token.Value().(string) 771 772 var listVal *ast.ObjectList 773 if ot, ok := item.Val.(*ast.ObjectType); ok { 774 listVal = ot.List 775 } else { 776 return nil, fmt.Errorf("resources %s[%s]: should be an object", t, k) 777 } 778 779 var config map[string]interface{} 780 if err := hcl.DecodeObject(&config, item.Val); err != nil { 781 return nil, fmt.Errorf( 782 "Error reading config for %s[%s]: %s", 783 t, 784 k, 785 err) 786 } 787 788 // Remove the fields we handle specially 789 delete(config, "connection") 790 delete(config, "count") 791 delete(config, "depends_on") 792 delete(config, "provisioner") 793 delete(config, "provider") 794 delete(config, "lifecycle") 795 796 rawConfig, err := NewRawConfig(config) 797 if err != nil { 798 return nil, fmt.Errorf( 799 "Error reading config for %s[%s]: %s", 800 t, 801 k, 802 err) 803 } 804 805 // If we have a count, then figure it out 806 var count string = "1" 807 if o := listVal.Filter("count"); len(o.Items) > 0 { 808 err = hcl.DecodeObject(&count, o.Items[0].Val) 809 if err != nil { 810 return nil, fmt.Errorf( 811 "Error parsing count for %s[%s]: %s", 812 t, 813 k, 814 err) 815 } 816 } 817 countConfig, err := NewRawConfig(map[string]interface{}{ 818 "count": count, 819 }) 820 if err != nil { 821 return nil, err 822 } 823 countConfig.Key = "count" 824 825 // If we have depends fields, then add those in 826 var dependsOn []string 827 if o := listVal.Filter("depends_on"); len(o.Items) > 0 { 828 err := hcl.DecodeObject(&dependsOn, o.Items[0].Val) 829 if err != nil { 830 return nil, fmt.Errorf( 831 "Error reading depends_on for %s[%s]: %s", 832 t, 833 k, 834 err) 835 } 836 } 837 838 // If we have connection info, then parse those out 839 var connInfo map[string]interface{} 840 if o := listVal.Filter("connection"); len(o.Items) > 0 { 841 err := hcl.DecodeObject(&connInfo, o.Items[0].Val) 842 if err != nil { 843 return nil, fmt.Errorf( 844 "Error reading connection info for %s[%s]: %s", 845 t, 846 k, 847 err) 848 } 849 } 850 851 // If we have provisioners, then parse those out 852 var provisioners []*Provisioner 853 if os := listVal.Filter("provisioner"); len(os.Items) > 0 { 854 var err error 855 provisioners, err = loadProvisionersHcl(os, connInfo) 856 if err != nil { 857 return nil, fmt.Errorf( 858 "Error reading provisioners for %s[%s]: %s", 859 t, 860 k, 861 err) 862 } 863 } 864 865 // If we have a provider, then parse it out 866 var provider string 867 if o := listVal.Filter("provider"); len(o.Items) > 0 { 868 err := hcl.DecodeObject(&provider, o.Items[0].Val) 869 if err != nil { 870 return nil, fmt.Errorf( 871 "Error reading provider for %s[%s]: %s", 872 t, 873 k, 874 err) 875 } 876 } 877 878 // Check if the resource should be re-created before 879 // destroying the existing instance 880 var lifecycle ResourceLifecycle 881 if o := listVal.Filter("lifecycle"); len(o.Items) > 0 { 882 if len(o.Items) > 1 { 883 return nil, fmt.Errorf( 884 "%s[%s]: Multiple lifecycle blocks found, expected one", 885 t, k) 886 } 887 888 // Check for invalid keys 889 valid := []string{"create_before_destroy", "ignore_changes", "prevent_destroy"} 890 if err := checkHCLKeys(o.Items[0].Val, valid); err != nil { 891 return nil, multierror.Prefix(err, fmt.Sprintf( 892 "%s[%s]:", t, k)) 893 } 894 895 var raw map[string]interface{} 896 if err = hcl.DecodeObject(&raw, o.Items[0].Val); err != nil { 897 return nil, fmt.Errorf( 898 "Error parsing lifecycle for %s[%s]: %s", 899 t, 900 k, 901 err) 902 } 903 904 if err := mapstructure.WeakDecode(raw, &lifecycle); err != nil { 905 return nil, fmt.Errorf( 906 "Error parsing lifecycle for %s[%s]: %s", 907 t, 908 k, 909 err) 910 } 911 } 912 913 result = append(result, &Resource{ 914 Mode: ManagedResourceMode, 915 Name: k, 916 Type: t, 917 RawCount: countConfig, 918 RawConfig: rawConfig, 919 Provisioners: provisioners, 920 Provider: provider, 921 DependsOn: dependsOn, 922 Lifecycle: lifecycle, 923 }) 924 } 925 926 return result, nil 927 } 928 929 func loadProvisionersHcl(list *ast.ObjectList, connInfo map[string]interface{}) ([]*Provisioner, error) { 930 if err := assertAllBlocksHaveNames("provisioner", list); err != nil { 931 return nil, err 932 } 933 934 list = list.Children() 935 if len(list.Items) == 0 { 936 return nil, nil 937 } 938 939 // Go through each object and turn it into an actual result. 940 result := make([]*Provisioner, 0, len(list.Items)) 941 for _, item := range list.Items { 942 n := item.Keys[0].Token.Value().(string) 943 944 var listVal *ast.ObjectList 945 if ot, ok := item.Val.(*ast.ObjectType); ok { 946 listVal = ot.List 947 } else { 948 return nil, fmt.Errorf("provisioner '%s': should be an object", n) 949 } 950 951 var config map[string]interface{} 952 if err := hcl.DecodeObject(&config, item.Val); err != nil { 953 return nil, err 954 } 955 956 // Parse the "when" value 957 when := ProvisionerWhenCreate 958 if v, ok := config["when"]; ok { 959 switch v { 960 case "create": 961 when = ProvisionerWhenCreate 962 case "destroy": 963 when = ProvisionerWhenDestroy 964 default: 965 return nil, fmt.Errorf( 966 "position %s: 'provisioner' when must be 'create' or 'destroy'", 967 item.Pos()) 968 } 969 } 970 971 // Parse the "on_failure" value 972 onFailure := ProvisionerOnFailureFail 973 if v, ok := config["on_failure"]; ok { 974 switch v { 975 case "continue": 976 onFailure = ProvisionerOnFailureContinue 977 case "fail": 978 onFailure = ProvisionerOnFailureFail 979 default: 980 return nil, fmt.Errorf( 981 "position %s: 'provisioner' on_failure must be 'continue' or 'fail'", 982 item.Pos()) 983 } 984 } 985 986 // Delete fields we special case 987 delete(config, "connection") 988 delete(config, "when") 989 delete(config, "on_failure") 990 991 rawConfig, err := NewRawConfig(config) 992 if err != nil { 993 return nil, err 994 } 995 996 // Check if we have a provisioner-level connection 997 // block that overrides the resource-level 998 var subConnInfo map[string]interface{} 999 if o := listVal.Filter("connection"); len(o.Items) > 0 { 1000 err := hcl.DecodeObject(&subConnInfo, o.Items[0].Val) 1001 if err != nil { 1002 return nil, err 1003 } 1004 } 1005 1006 // Inherit from the resource connInfo any keys 1007 // that are not explicitly overriden. 1008 if connInfo != nil && subConnInfo != nil { 1009 for k, v := range connInfo { 1010 if _, ok := subConnInfo[k]; !ok { 1011 subConnInfo[k] = v 1012 } 1013 } 1014 } else if subConnInfo == nil { 1015 subConnInfo = connInfo 1016 } 1017 1018 // Parse the connInfo 1019 connRaw, err := NewRawConfig(subConnInfo) 1020 if err != nil { 1021 return nil, err 1022 } 1023 1024 result = append(result, &Provisioner{ 1025 Type: n, 1026 RawConfig: rawConfig, 1027 ConnInfo: connRaw, 1028 When: when, 1029 OnFailure: onFailure, 1030 }) 1031 } 1032 1033 return result, nil 1034 } 1035 1036 /* 1037 func hclObjectMap(os *hclobj.Object) map[string]ast.ListNode { 1038 objects := make(map[string][]*hclobj.Object) 1039 1040 for _, o := range os.Elem(false) { 1041 for _, elem := range o.Elem(true) { 1042 val, ok := objects[elem.Key] 1043 if !ok { 1044 val = make([]*hclobj.Object, 0, 1) 1045 } 1046 1047 val = append(val, elem) 1048 objects[elem.Key] = val 1049 } 1050 } 1051 1052 return objects 1053 } 1054 */ 1055 1056 // assertAllBlocksHaveNames returns an error if any of the items in 1057 // the given object list are blocks without keys (like "module {}") 1058 // or simple assignments (like "module = 1"). It returns nil if 1059 // neither of these things are true. 1060 // 1061 // The given name is used in any generated error messages, and should 1062 // be the name of the block we're dealing with. The given list should 1063 // be the result of calling .Filter on an object list with that same 1064 // name. 1065 func assertAllBlocksHaveNames(name string, list *ast.ObjectList) error { 1066 if elem := list.Elem(); len(elem.Items) != 0 { 1067 switch et := elem.Items[0].Val.(type) { 1068 case *ast.ObjectType: 1069 pos := et.Lbrace 1070 return fmt.Errorf("%s: %q must be followed by a name", pos, name) 1071 default: 1072 pos := elem.Items[0].Val.Pos() 1073 return fmt.Errorf("%s: %q must be a configuration block", pos, name) 1074 } 1075 } 1076 return nil 1077 } 1078 1079 func checkHCLKeys(node ast.Node, valid []string) error { 1080 var list *ast.ObjectList 1081 switch n := node.(type) { 1082 case *ast.ObjectList: 1083 list = n 1084 case *ast.ObjectType: 1085 list = n.List 1086 default: 1087 return fmt.Errorf("cannot check HCL keys of type %T", n) 1088 } 1089 1090 validMap := make(map[string]struct{}, len(valid)) 1091 for _, v := range valid { 1092 validMap[v] = struct{}{} 1093 } 1094 1095 var result error 1096 for _, item := range list.Items { 1097 key := item.Keys[0].Token.Value().(string) 1098 if _, ok := validMap[key]; !ok { 1099 result = multierror.Append(result, fmt.Errorf( 1100 "invalid key: %s", key)) 1101 } 1102 } 1103 1104 return result 1105 } 1106 1107 // unwrapHCLObjectKeysFromJSON cleans up an edge case that can occur when 1108 // parsing JSON as input: if we're parsing JSON then directly nested 1109 // items will show up as additional "keys". 1110 // 1111 // For objects that expect a fixed number of keys, this breaks the 1112 // decoding process. This function unwraps the object into what it would've 1113 // looked like if it came directly from HCL by specifying the number of keys 1114 // you expect. 1115 // 1116 // Example: 1117 // 1118 // { "foo": { "baz": {} } } 1119 // 1120 // Will show up with Keys being: []string{"foo", "baz"} 1121 // when we really just want the first two. This function will fix this. 1122 func unwrapHCLObjectKeysFromJSON(item *ast.ObjectItem, depth int) { 1123 if len(item.Keys) > depth && item.Keys[0].Token.JSON { 1124 for len(item.Keys) > depth { 1125 // Pop off the last key 1126 n := len(item.Keys) 1127 key := item.Keys[n-1] 1128 item.Keys[n-1] = nil 1129 item.Keys = item.Keys[:n-1] 1130 1131 // Wrap our value in a list 1132 item.Val = &ast.ObjectType{ 1133 List: &ast.ObjectList{ 1134 Items: []*ast.ObjectItem{ 1135 &ast.ObjectItem{ 1136 Keys: []*ast.ObjectKey{key}, 1137 Val: item.Val, 1138 }, 1139 }, 1140 }, 1141 } 1142 } 1143 } 1144 }