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