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