github.com/ojiry/terraform@v0.8.2-0.20161218223921-e50cec712c4a/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 // NOTE: We purposely don't validate unknown HCL keys here so that 213 // we can potentially read _future_ Terraform version config (to 214 // still be able to validate the required version). 215 // 216 // We should still keep track of unknown keys to validate later, but 217 // HCL doesn't currently support that. 218 219 var config Terraform 220 if err := hcl.DecodeObject(&config, item.Val); err != nil { 221 return nil, fmt.Errorf( 222 "Error reading terraform config: %s", 223 err) 224 } 225 226 return &config, nil 227 } 228 229 // Given a handle to a HCL object, this transforms it into the Atlas 230 // configuration. 231 func loadAtlasHcl(list *ast.ObjectList) (*AtlasConfig, error) { 232 if len(list.Items) > 1 { 233 return nil, fmt.Errorf("only one 'atlas' block allowed") 234 } 235 236 // Get our one item 237 item := list.Items[0] 238 239 var config AtlasConfig 240 if err := hcl.DecodeObject(&config, item.Val); err != nil { 241 return nil, fmt.Errorf( 242 "Error reading atlas config: %s", 243 err) 244 } 245 246 return &config, nil 247 } 248 249 // Given a handle to a HCL object, this recurses into the structure 250 // and pulls out a list of modules. 251 // 252 // The resulting modules may not be unique, but each module 253 // represents exactly one module definition in the HCL configuration. 254 // We leave it up to another pass to merge them together. 255 func loadModulesHcl(list *ast.ObjectList) ([]*Module, error) { 256 list = list.Children() 257 if len(list.Items) == 0 { 258 return nil, nil 259 } 260 261 // Where all the results will go 262 var result []*Module 263 264 // Now go over all the types and their children in order to get 265 // all of the actual resources. 266 for _, item := range list.Items { 267 k := item.Keys[0].Token.Value().(string) 268 269 var listVal *ast.ObjectList 270 if ot, ok := item.Val.(*ast.ObjectType); ok { 271 listVal = ot.List 272 } else { 273 return nil, fmt.Errorf("module '%s': should be an object", k) 274 } 275 276 var config map[string]interface{} 277 if err := hcl.DecodeObject(&config, item.Val); err != nil { 278 return nil, fmt.Errorf( 279 "Error reading config for %s: %s", 280 k, 281 err) 282 } 283 284 // Remove the fields we handle specially 285 delete(config, "source") 286 287 rawConfig, err := NewRawConfig(config) 288 if err != nil { 289 return nil, fmt.Errorf( 290 "Error reading config for %s: %s", 291 k, 292 err) 293 } 294 295 // If we have a count, then figure it out 296 var source string 297 if o := listVal.Filter("source"); len(o.Items) > 0 { 298 err = hcl.DecodeObject(&source, o.Items[0].Val) 299 if err != nil { 300 return nil, fmt.Errorf( 301 "Error parsing source for %s: %s", 302 k, 303 err) 304 } 305 } 306 307 result = append(result, &Module{ 308 Name: k, 309 Source: source, 310 RawConfig: rawConfig, 311 }) 312 } 313 314 return result, nil 315 } 316 317 // LoadOutputsHcl recurses into the given HCL object and turns 318 // it into a mapping of outputs. 319 func loadOutputsHcl(list *ast.ObjectList) ([]*Output, error) { 320 list = list.Children() 321 if len(list.Items) == 0 { 322 return nil, fmt.Errorf( 323 "'output' must be followed by exactly one string: a name") 324 } 325 326 // Go through each object and turn it into an actual result. 327 result := make([]*Output, 0, len(list.Items)) 328 for _, item := range list.Items { 329 n := item.Keys[0].Token.Value().(string) 330 331 var listVal *ast.ObjectList 332 if ot, ok := item.Val.(*ast.ObjectType); ok { 333 listVal = ot.List 334 } else { 335 return nil, fmt.Errorf("output '%s': should be an object", n) 336 } 337 338 var config map[string]interface{} 339 if err := hcl.DecodeObject(&config, item.Val); err != nil { 340 return nil, err 341 } 342 343 // Delete special keys 344 delete(config, "depends_on") 345 346 rawConfig, err := NewRawConfig(config) 347 if err != nil { 348 return nil, fmt.Errorf( 349 "Error reading config for output %s: %s", 350 n, 351 err) 352 } 353 354 // If we have depends fields, then add those in 355 var dependsOn []string 356 if o := listVal.Filter("depends_on"); len(o.Items) > 0 { 357 err := hcl.DecodeObject(&dependsOn, o.Items[0].Val) 358 if err != nil { 359 return nil, fmt.Errorf( 360 "Error reading depends_on for output %q: %s", 361 n, 362 err) 363 } 364 } 365 366 result = append(result, &Output{ 367 Name: n, 368 RawConfig: rawConfig, 369 DependsOn: dependsOn, 370 }) 371 } 372 373 return result, nil 374 } 375 376 // LoadVariablesHcl recurses into the given HCL object and turns 377 // it into a list of variables. 378 func loadVariablesHcl(list *ast.ObjectList) ([]*Variable, error) { 379 list = list.Children() 380 if len(list.Items) == 0 { 381 return nil, fmt.Errorf( 382 "'variable' must be followed by exactly one strings: a name") 383 } 384 385 // hclVariable is the structure each variable is decoded into 386 type hclVariable struct { 387 DeclaredType string `hcl:"type"` 388 Default interface{} 389 Description string 390 Fields []string `hcl:",decodedFields"` 391 } 392 393 // Go through each object and turn it into an actual result. 394 result := make([]*Variable, 0, len(list.Items)) 395 for _, item := range list.Items { 396 // Clean up items from JSON 397 unwrapHCLObjectKeysFromJSON(item, 1) 398 399 // Verify the keys 400 if len(item.Keys) != 1 { 401 return nil, fmt.Errorf( 402 "position %s: 'variable' must be followed by exactly one strings: a name", 403 item.Pos()) 404 } 405 406 n := item.Keys[0].Token.Value().(string) 407 if !NameRegexp.MatchString(n) { 408 return nil, fmt.Errorf( 409 "position %s: 'variable' name must match regular expression: %s", 410 item.Pos(), NameRegexp) 411 } 412 413 // Check for invalid keys 414 valid := []string{"type", "default", "description"} 415 if err := checkHCLKeys(item.Val, valid); err != nil { 416 return nil, multierror.Prefix(err, fmt.Sprintf( 417 "variable[%s]:", n)) 418 } 419 420 // Decode into hclVariable to get typed values 421 var hclVar hclVariable 422 if err := hcl.DecodeObject(&hclVar, item.Val); err != nil { 423 return nil, err 424 } 425 426 // Defaults turn into a slice of map[string]interface{} and 427 // we need to make sure to convert that down into the 428 // proper type for Config. 429 if ms, ok := hclVar.Default.([]map[string]interface{}); ok { 430 def := make(map[string]interface{}) 431 for _, m := range ms { 432 for k, v := range m { 433 def[k] = v 434 } 435 } 436 437 hclVar.Default = def 438 } 439 440 // Build the new variable and do some basic validation 441 newVar := &Variable{ 442 Name: n, 443 DeclaredType: hclVar.DeclaredType, 444 Default: hclVar.Default, 445 Description: hclVar.Description, 446 } 447 if err := newVar.ValidateTypeAndDefault(); err != nil { 448 return nil, err 449 } 450 451 result = append(result, newVar) 452 } 453 454 return result, nil 455 } 456 457 // LoadProvidersHcl recurses into the given HCL object and turns 458 // it into a mapping of provider configs. 459 func loadProvidersHcl(list *ast.ObjectList) ([]*ProviderConfig, error) { 460 list = list.Children() 461 if len(list.Items) == 0 { 462 return nil, nil 463 } 464 465 // Go through each object and turn it into an actual result. 466 result := make([]*ProviderConfig, 0, len(list.Items)) 467 for _, item := range list.Items { 468 n := item.Keys[0].Token.Value().(string) 469 470 var listVal *ast.ObjectList 471 if ot, ok := item.Val.(*ast.ObjectType); ok { 472 listVal = ot.List 473 } else { 474 return nil, fmt.Errorf("module '%s': should be an object", n) 475 } 476 477 var config map[string]interface{} 478 if err := hcl.DecodeObject(&config, item.Val); err != nil { 479 return nil, err 480 } 481 482 delete(config, "alias") 483 484 rawConfig, err := NewRawConfig(config) 485 if err != nil { 486 return nil, fmt.Errorf( 487 "Error reading config for provider config %s: %s", 488 n, 489 err) 490 } 491 492 // If we have an alias field, then add those in 493 var alias string 494 if a := listVal.Filter("alias"); len(a.Items) > 0 { 495 err := hcl.DecodeObject(&alias, a.Items[0].Val) 496 if err != nil { 497 return nil, fmt.Errorf( 498 "Error reading alias for provider[%s]: %s", 499 n, 500 err) 501 } 502 } 503 504 result = append(result, &ProviderConfig{ 505 Name: n, 506 Alias: alias, 507 RawConfig: rawConfig, 508 }) 509 } 510 511 return result, nil 512 } 513 514 // Given a handle to a HCL object, this recurses into the structure 515 // and pulls out a list of data sources. 516 // 517 // The resulting data sources may not be unique, but each one 518 // represents exactly one data definition in the HCL configuration. 519 // We leave it up to another pass to merge them together. 520 func loadDataResourcesHcl(list *ast.ObjectList) ([]*Resource, error) { 521 list = list.Children() 522 if len(list.Items) == 0 { 523 return nil, nil 524 } 525 526 // Where all the results will go 527 var result []*Resource 528 529 // Now go over all the types and their children in order to get 530 // all of the actual resources. 531 for _, item := range list.Items { 532 if len(item.Keys) != 2 { 533 return nil, fmt.Errorf( 534 "position %s: 'data' must be followed by exactly two strings: a type and a name", 535 item.Pos()) 536 } 537 538 t := item.Keys[0].Token.Value().(string) 539 k := item.Keys[1].Token.Value().(string) 540 541 var listVal *ast.ObjectList 542 if ot, ok := item.Val.(*ast.ObjectType); ok { 543 listVal = ot.List 544 } else { 545 return nil, fmt.Errorf("data sources %s[%s]: should be an object", t, k) 546 } 547 548 var config map[string]interface{} 549 if err := hcl.DecodeObject(&config, item.Val); err != nil { 550 return nil, fmt.Errorf( 551 "Error reading config for %s[%s]: %s", 552 t, 553 k, 554 err) 555 } 556 557 // Remove the fields we handle specially 558 delete(config, "depends_on") 559 delete(config, "provider") 560 delete(config, "count") 561 562 rawConfig, err := NewRawConfig(config) 563 if err != nil { 564 return nil, fmt.Errorf( 565 "Error reading config for %s[%s]: %s", 566 t, 567 k, 568 err) 569 } 570 571 // If we have a count, then figure it out 572 var count string = "1" 573 if o := listVal.Filter("count"); len(o.Items) > 0 { 574 err = hcl.DecodeObject(&count, o.Items[0].Val) 575 if err != nil { 576 return nil, fmt.Errorf( 577 "Error parsing count for %s[%s]: %s", 578 t, 579 k, 580 err) 581 } 582 } 583 countConfig, err := NewRawConfig(map[string]interface{}{ 584 "count": count, 585 }) 586 if err != nil { 587 return nil, err 588 } 589 countConfig.Key = "count" 590 591 // If we have depends fields, then add those in 592 var dependsOn []string 593 if o := listVal.Filter("depends_on"); len(o.Items) > 0 { 594 err := hcl.DecodeObject(&dependsOn, o.Items[0].Val) 595 if err != nil { 596 return nil, fmt.Errorf( 597 "Error reading depends_on for %s[%s]: %s", 598 t, 599 k, 600 err) 601 } 602 } 603 604 // If we have a provider, then parse it out 605 var provider string 606 if o := listVal.Filter("provider"); len(o.Items) > 0 { 607 err := hcl.DecodeObject(&provider, o.Items[0].Val) 608 if err != nil { 609 return nil, fmt.Errorf( 610 "Error reading provider for %s[%s]: %s", 611 t, 612 k, 613 err) 614 } 615 } 616 617 result = append(result, &Resource{ 618 Mode: DataResourceMode, 619 Name: k, 620 Type: t, 621 RawCount: countConfig, 622 RawConfig: rawConfig, 623 Provider: provider, 624 Provisioners: []*Provisioner{}, 625 DependsOn: dependsOn, 626 Lifecycle: ResourceLifecycle{}, 627 }) 628 } 629 630 return result, nil 631 } 632 633 // Given a handle to a HCL object, this recurses into the structure 634 // and pulls out a list of managed resources. 635 // 636 // The resulting resources may not be unique, but each resource 637 // represents exactly one "resource" block in the HCL configuration. 638 // We leave it up to another pass to merge them together. 639 func loadManagedResourcesHcl(list *ast.ObjectList) ([]*Resource, error) { 640 list = list.Children() 641 if len(list.Items) == 0 { 642 return nil, nil 643 } 644 645 // Where all the results will go 646 var result []*Resource 647 648 // Now go over all the types and their children in order to get 649 // all of the actual resources. 650 for _, item := range list.Items { 651 // GH-4385: We detect a pure provisioner resource and give the user 652 // an error about how to do it cleanly. 653 if len(item.Keys) == 4 && item.Keys[2].Token.Value().(string) == "provisioner" { 654 return nil, fmt.Errorf( 655 "position %s: provisioners in a resource should be wrapped in a list\n\n"+ 656 "Example: \"provisioner\": [ { \"local-exec\": ... } ]", 657 item.Pos()) 658 } 659 660 // Fix up JSON input 661 unwrapHCLObjectKeysFromJSON(item, 2) 662 663 if len(item.Keys) != 2 { 664 return nil, fmt.Errorf( 665 "position %s: resource must be followed by exactly two strings, a type and a name", 666 item.Pos()) 667 } 668 669 t := item.Keys[0].Token.Value().(string) 670 k := item.Keys[1].Token.Value().(string) 671 672 var listVal *ast.ObjectList 673 if ot, ok := item.Val.(*ast.ObjectType); ok { 674 listVal = ot.List 675 } else { 676 return nil, fmt.Errorf("resources %s[%s]: should be an object", t, k) 677 } 678 679 var config map[string]interface{} 680 if err := hcl.DecodeObject(&config, item.Val); err != nil { 681 return nil, fmt.Errorf( 682 "Error reading config for %s[%s]: %s", 683 t, 684 k, 685 err) 686 } 687 688 // Remove the fields we handle specially 689 delete(config, "connection") 690 delete(config, "count") 691 delete(config, "depends_on") 692 delete(config, "provisioner") 693 delete(config, "provider") 694 delete(config, "lifecycle") 695 696 rawConfig, err := NewRawConfig(config) 697 if err != nil { 698 return nil, fmt.Errorf( 699 "Error reading config for %s[%s]: %s", 700 t, 701 k, 702 err) 703 } 704 705 // If we have a count, then figure it out 706 var count string = "1" 707 if o := listVal.Filter("count"); len(o.Items) > 0 { 708 err = hcl.DecodeObject(&count, o.Items[0].Val) 709 if err != nil { 710 return nil, fmt.Errorf( 711 "Error parsing count for %s[%s]: %s", 712 t, 713 k, 714 err) 715 } 716 } 717 countConfig, err := NewRawConfig(map[string]interface{}{ 718 "count": count, 719 }) 720 if err != nil { 721 return nil, err 722 } 723 countConfig.Key = "count" 724 725 // If we have depends fields, then add those in 726 var dependsOn []string 727 if o := listVal.Filter("depends_on"); len(o.Items) > 0 { 728 err := hcl.DecodeObject(&dependsOn, o.Items[0].Val) 729 if err != nil { 730 return nil, fmt.Errorf( 731 "Error reading depends_on for %s[%s]: %s", 732 t, 733 k, 734 err) 735 } 736 } 737 738 // If we have connection info, then parse those out 739 var connInfo map[string]interface{} 740 if o := listVal.Filter("connection"); len(o.Items) > 0 { 741 err := hcl.DecodeObject(&connInfo, o.Items[0].Val) 742 if err != nil { 743 return nil, fmt.Errorf( 744 "Error reading connection info for %s[%s]: %s", 745 t, 746 k, 747 err) 748 } 749 } 750 751 // If we have provisioners, then parse those out 752 var provisioners []*Provisioner 753 if os := listVal.Filter("provisioner"); len(os.Items) > 0 { 754 var err error 755 provisioners, err = loadProvisionersHcl(os, connInfo) 756 if err != nil { 757 return nil, fmt.Errorf( 758 "Error reading provisioners for %s[%s]: %s", 759 t, 760 k, 761 err) 762 } 763 } 764 765 // If we have a provider, then parse it out 766 var provider string 767 if o := listVal.Filter("provider"); len(o.Items) > 0 { 768 err := hcl.DecodeObject(&provider, o.Items[0].Val) 769 if err != nil { 770 return nil, fmt.Errorf( 771 "Error reading provider for %s[%s]: %s", 772 t, 773 k, 774 err) 775 } 776 } 777 778 // Check if the resource should be re-created before 779 // destroying the existing instance 780 var lifecycle ResourceLifecycle 781 if o := listVal.Filter("lifecycle"); len(o.Items) > 0 { 782 if len(o.Items) > 1 { 783 return nil, fmt.Errorf( 784 "%s[%s]: Multiple lifecycle blocks found, expected one", 785 t, k) 786 } 787 788 // Check for invalid keys 789 valid := []string{"create_before_destroy", "ignore_changes", "prevent_destroy"} 790 if err := checkHCLKeys(o.Items[0].Val, valid); err != nil { 791 return nil, multierror.Prefix(err, fmt.Sprintf( 792 "%s[%s]:", t, k)) 793 } 794 795 var raw map[string]interface{} 796 if err = hcl.DecodeObject(&raw, o.Items[0].Val); err != nil { 797 return nil, fmt.Errorf( 798 "Error parsing lifecycle for %s[%s]: %s", 799 t, 800 k, 801 err) 802 } 803 804 if err := mapstructure.WeakDecode(raw, &lifecycle); err != nil { 805 return nil, fmt.Errorf( 806 "Error parsing lifecycle for %s[%s]: %s", 807 t, 808 k, 809 err) 810 } 811 } 812 813 result = append(result, &Resource{ 814 Mode: ManagedResourceMode, 815 Name: k, 816 Type: t, 817 RawCount: countConfig, 818 RawConfig: rawConfig, 819 Provisioners: provisioners, 820 Provider: provider, 821 DependsOn: dependsOn, 822 Lifecycle: lifecycle, 823 }) 824 } 825 826 return result, nil 827 } 828 829 func loadProvisionersHcl(list *ast.ObjectList, connInfo map[string]interface{}) ([]*Provisioner, error) { 830 list = list.Children() 831 if len(list.Items) == 0 { 832 return nil, nil 833 } 834 835 // Go through each object and turn it into an actual result. 836 result := make([]*Provisioner, 0, len(list.Items)) 837 for _, item := range list.Items { 838 n := item.Keys[0].Token.Value().(string) 839 840 var listVal *ast.ObjectList 841 if ot, ok := item.Val.(*ast.ObjectType); ok { 842 listVal = ot.List 843 } else { 844 return nil, fmt.Errorf("provisioner '%s': should be an object", n) 845 } 846 847 var config map[string]interface{} 848 if err := hcl.DecodeObject(&config, item.Val); err != nil { 849 return nil, err 850 } 851 852 // Delete the "connection" section, handle separately 853 delete(config, "connection") 854 855 rawConfig, err := NewRawConfig(config) 856 if err != nil { 857 return nil, err 858 } 859 860 // Check if we have a provisioner-level connection 861 // block that overrides the resource-level 862 var subConnInfo map[string]interface{} 863 if o := listVal.Filter("connection"); len(o.Items) > 0 { 864 err := hcl.DecodeObject(&subConnInfo, o.Items[0].Val) 865 if err != nil { 866 return nil, err 867 } 868 } 869 870 // Inherit from the resource connInfo any keys 871 // that are not explicitly overriden. 872 if connInfo != nil && subConnInfo != nil { 873 for k, v := range connInfo { 874 if _, ok := subConnInfo[k]; !ok { 875 subConnInfo[k] = v 876 } 877 } 878 } else if subConnInfo == nil { 879 subConnInfo = connInfo 880 } 881 882 // Parse the connInfo 883 connRaw, err := NewRawConfig(subConnInfo) 884 if err != nil { 885 return nil, err 886 } 887 888 result = append(result, &Provisioner{ 889 Type: n, 890 RawConfig: rawConfig, 891 ConnInfo: connRaw, 892 }) 893 } 894 895 return result, nil 896 } 897 898 /* 899 func hclObjectMap(os *hclobj.Object) map[string]ast.ListNode { 900 objects := make(map[string][]*hclobj.Object) 901 902 for _, o := range os.Elem(false) { 903 for _, elem := range o.Elem(true) { 904 val, ok := objects[elem.Key] 905 if !ok { 906 val = make([]*hclobj.Object, 0, 1) 907 } 908 909 val = append(val, elem) 910 objects[elem.Key] = val 911 } 912 } 913 914 return objects 915 } 916 */ 917 918 func checkHCLKeys(node ast.Node, valid []string) error { 919 var list *ast.ObjectList 920 switch n := node.(type) { 921 case *ast.ObjectList: 922 list = n 923 case *ast.ObjectType: 924 list = n.List 925 default: 926 return fmt.Errorf("cannot check HCL keys of type %T", n) 927 } 928 929 validMap := make(map[string]struct{}, len(valid)) 930 for _, v := range valid { 931 validMap[v] = struct{}{} 932 } 933 934 var result error 935 for _, item := range list.Items { 936 key := item.Keys[0].Token.Value().(string) 937 if _, ok := validMap[key]; !ok { 938 result = multierror.Append(result, fmt.Errorf( 939 "invalid key: %s", key)) 940 } 941 } 942 943 return result 944 } 945 946 // unwrapHCLObjectKeysFromJSON cleans up an edge case that can occur when 947 // parsing JSON as input: if we're parsing JSON then directly nested 948 // items will show up as additional "keys". 949 // 950 // For objects that expect a fixed number of keys, this breaks the 951 // decoding process. This function unwraps the object into what it would've 952 // looked like if it came directly from HCL by specifying the number of keys 953 // you expect. 954 // 955 // Example: 956 // 957 // { "foo": { "baz": {} } } 958 // 959 // Will show up with Keys being: []string{"foo", "baz"} 960 // when we really just want the first two. This function will fix this. 961 func unwrapHCLObjectKeysFromJSON(item *ast.ObjectItem, depth int) { 962 if len(item.Keys) > depth && item.Keys[0].Token.JSON { 963 for len(item.Keys) > depth { 964 // Pop off the last key 965 n := len(item.Keys) 966 key := item.Keys[n-1] 967 item.Keys[n-1] = nil 968 item.Keys = item.Keys[:n-1] 969 970 // Wrap our value in a list 971 item.Val = &ast.ObjectType{ 972 List: &ast.ObjectList{ 973 Items: []*ast.ObjectItem{ 974 &ast.ObjectItem{ 975 Keys: []*ast.ObjectKey{key}, 976 Val: item.Val, 977 }, 978 }, 979 }, 980 } 981 } 982 } 983 }