github.com/rhenning/terraform@v0.8.0-beta2/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 408 /* 409 // TODO: catch extra fields 410 // Decode into raw map[string]interface{} so we know ALL fields 411 var config map[string]interface{} 412 if err := hcl.DecodeObject(&config, item.Val); err != nil { 413 return nil, err 414 } 415 */ 416 417 // Decode into hclVariable to get typed values 418 var hclVar hclVariable 419 if err := hcl.DecodeObject(&hclVar, item.Val); err != nil { 420 return nil, err 421 } 422 423 // Defaults turn into a slice of map[string]interface{} and 424 // we need to make sure to convert that down into the 425 // proper type for Config. 426 if ms, ok := hclVar.Default.([]map[string]interface{}); ok { 427 def := make(map[string]interface{}) 428 for _, m := range ms { 429 for k, v := range m { 430 def[k] = v 431 } 432 } 433 434 hclVar.Default = def 435 } 436 437 // Build the new variable and do some basic validation 438 newVar := &Variable{ 439 Name: n, 440 DeclaredType: hclVar.DeclaredType, 441 Default: hclVar.Default, 442 Description: hclVar.Description, 443 } 444 if err := newVar.ValidateTypeAndDefault(); err != nil { 445 return nil, err 446 } 447 448 result = append(result, newVar) 449 } 450 451 return result, nil 452 } 453 454 // LoadProvidersHcl recurses into the given HCL object and turns 455 // it into a mapping of provider configs. 456 func loadProvidersHcl(list *ast.ObjectList) ([]*ProviderConfig, error) { 457 list = list.Children() 458 if len(list.Items) == 0 { 459 return nil, nil 460 } 461 462 // Go through each object and turn it into an actual result. 463 result := make([]*ProviderConfig, 0, len(list.Items)) 464 for _, item := range list.Items { 465 n := item.Keys[0].Token.Value().(string) 466 467 var listVal *ast.ObjectList 468 if ot, ok := item.Val.(*ast.ObjectType); ok { 469 listVal = ot.List 470 } else { 471 return nil, fmt.Errorf("module '%s': should be an object", n) 472 } 473 474 var config map[string]interface{} 475 if err := hcl.DecodeObject(&config, item.Val); err != nil { 476 return nil, err 477 } 478 479 delete(config, "alias") 480 481 rawConfig, err := NewRawConfig(config) 482 if err != nil { 483 return nil, fmt.Errorf( 484 "Error reading config for provider config %s: %s", 485 n, 486 err) 487 } 488 489 // If we have an alias field, then add those in 490 var alias string 491 if a := listVal.Filter("alias"); len(a.Items) > 0 { 492 err := hcl.DecodeObject(&alias, a.Items[0].Val) 493 if err != nil { 494 return nil, fmt.Errorf( 495 "Error reading alias for provider[%s]: %s", 496 n, 497 err) 498 } 499 } 500 501 result = append(result, &ProviderConfig{ 502 Name: n, 503 Alias: alias, 504 RawConfig: rawConfig, 505 }) 506 } 507 508 return result, nil 509 } 510 511 // Given a handle to a HCL object, this recurses into the structure 512 // and pulls out a list of data sources. 513 // 514 // The resulting data sources may not be unique, but each one 515 // represents exactly one data definition in the HCL configuration. 516 // We leave it up to another pass to merge them together. 517 func loadDataResourcesHcl(list *ast.ObjectList) ([]*Resource, error) { 518 list = list.Children() 519 if len(list.Items) == 0 { 520 return nil, nil 521 } 522 523 // Where all the results will go 524 var result []*Resource 525 526 // Now go over all the types and their children in order to get 527 // all of the actual resources. 528 for _, item := range list.Items { 529 if len(item.Keys) != 2 { 530 return nil, fmt.Errorf( 531 "position %s: 'data' must be followed by exactly two strings: a type and a name", 532 item.Pos()) 533 } 534 535 t := item.Keys[0].Token.Value().(string) 536 k := item.Keys[1].Token.Value().(string) 537 538 var listVal *ast.ObjectList 539 if ot, ok := item.Val.(*ast.ObjectType); ok { 540 listVal = ot.List 541 } else { 542 return nil, fmt.Errorf("data sources %s[%s]: should be an object", t, k) 543 } 544 545 var config map[string]interface{} 546 if err := hcl.DecodeObject(&config, item.Val); err != nil { 547 return nil, fmt.Errorf( 548 "Error reading config for %s[%s]: %s", 549 t, 550 k, 551 err) 552 } 553 554 // Remove the fields we handle specially 555 delete(config, "depends_on") 556 delete(config, "provider") 557 delete(config, "count") 558 559 rawConfig, err := NewRawConfig(config) 560 if err != nil { 561 return nil, fmt.Errorf( 562 "Error reading config for %s[%s]: %s", 563 t, 564 k, 565 err) 566 } 567 568 // If we have a count, then figure it out 569 var count string = "1" 570 if o := listVal.Filter("count"); len(o.Items) > 0 { 571 err = hcl.DecodeObject(&count, o.Items[0].Val) 572 if err != nil { 573 return nil, fmt.Errorf( 574 "Error parsing count for %s[%s]: %s", 575 t, 576 k, 577 err) 578 } 579 } 580 countConfig, err := NewRawConfig(map[string]interface{}{ 581 "count": count, 582 }) 583 if err != nil { 584 return nil, err 585 } 586 countConfig.Key = "count" 587 588 // If we have depends fields, then add those in 589 var dependsOn []string 590 if o := listVal.Filter("depends_on"); len(o.Items) > 0 { 591 err := hcl.DecodeObject(&dependsOn, o.Items[0].Val) 592 if err != nil { 593 return nil, fmt.Errorf( 594 "Error reading depends_on for %s[%s]: %s", 595 t, 596 k, 597 err) 598 } 599 } 600 601 // If we have a provider, then parse it out 602 var provider string 603 if o := listVal.Filter("provider"); len(o.Items) > 0 { 604 err := hcl.DecodeObject(&provider, o.Items[0].Val) 605 if err != nil { 606 return nil, fmt.Errorf( 607 "Error reading provider for %s[%s]: %s", 608 t, 609 k, 610 err) 611 } 612 } 613 614 result = append(result, &Resource{ 615 Mode: DataResourceMode, 616 Name: k, 617 Type: t, 618 RawCount: countConfig, 619 RawConfig: rawConfig, 620 Provider: provider, 621 Provisioners: []*Provisioner{}, 622 DependsOn: dependsOn, 623 Lifecycle: ResourceLifecycle{}, 624 }) 625 } 626 627 return result, nil 628 } 629 630 // Given a handle to a HCL object, this recurses into the structure 631 // and pulls out a list of managed resources. 632 // 633 // The resulting resources may not be unique, but each resource 634 // represents exactly one "resource" block in the HCL configuration. 635 // We leave it up to another pass to merge them together. 636 func loadManagedResourcesHcl(list *ast.ObjectList) ([]*Resource, error) { 637 list = list.Children() 638 if len(list.Items) == 0 { 639 return nil, nil 640 } 641 642 // Where all the results will go 643 var result []*Resource 644 645 // Now go over all the types and their children in order to get 646 // all of the actual resources. 647 for _, item := range list.Items { 648 // GH-4385: We detect a pure provisioner resource and give the user 649 // an error about how to do it cleanly. 650 if len(item.Keys) == 4 && item.Keys[2].Token.Value().(string) == "provisioner" { 651 return nil, fmt.Errorf( 652 "position %s: provisioners in a resource should be wrapped in a list\n\n"+ 653 "Example: \"provisioner\": [ { \"local-exec\": ... } ]", 654 item.Pos()) 655 } 656 657 // Fix up JSON input 658 unwrapHCLObjectKeysFromJSON(item, 2) 659 660 if len(item.Keys) != 2 { 661 return nil, fmt.Errorf( 662 "position %s: resource must be followed by exactly two strings, a type and a name", 663 item.Pos()) 664 } 665 666 t := item.Keys[0].Token.Value().(string) 667 k := item.Keys[1].Token.Value().(string) 668 669 var listVal *ast.ObjectList 670 if ot, ok := item.Val.(*ast.ObjectType); ok { 671 listVal = ot.List 672 } else { 673 return nil, fmt.Errorf("resources %s[%s]: should be an object", t, k) 674 } 675 676 var config map[string]interface{} 677 if err := hcl.DecodeObject(&config, item.Val); err != nil { 678 return nil, fmt.Errorf( 679 "Error reading config for %s[%s]: %s", 680 t, 681 k, 682 err) 683 } 684 685 // Remove the fields we handle specially 686 delete(config, "connection") 687 delete(config, "count") 688 delete(config, "depends_on") 689 delete(config, "provisioner") 690 delete(config, "provider") 691 delete(config, "lifecycle") 692 693 rawConfig, err := NewRawConfig(config) 694 if err != nil { 695 return nil, fmt.Errorf( 696 "Error reading config for %s[%s]: %s", 697 t, 698 k, 699 err) 700 } 701 702 // If we have a count, then figure it out 703 var count string = "1" 704 if o := listVal.Filter("count"); len(o.Items) > 0 { 705 err = hcl.DecodeObject(&count, o.Items[0].Val) 706 if err != nil { 707 return nil, fmt.Errorf( 708 "Error parsing count for %s[%s]: %s", 709 t, 710 k, 711 err) 712 } 713 } 714 countConfig, err := NewRawConfig(map[string]interface{}{ 715 "count": count, 716 }) 717 if err != nil { 718 return nil, err 719 } 720 countConfig.Key = "count" 721 722 // If we have depends fields, then add those in 723 var dependsOn []string 724 if o := listVal.Filter("depends_on"); len(o.Items) > 0 { 725 err := hcl.DecodeObject(&dependsOn, o.Items[0].Val) 726 if err != nil { 727 return nil, fmt.Errorf( 728 "Error reading depends_on for %s[%s]: %s", 729 t, 730 k, 731 err) 732 } 733 } 734 735 // If we have connection info, then parse those out 736 var connInfo map[string]interface{} 737 if o := listVal.Filter("connection"); len(o.Items) > 0 { 738 err := hcl.DecodeObject(&connInfo, o.Items[0].Val) 739 if err != nil { 740 return nil, fmt.Errorf( 741 "Error reading connection info for %s[%s]: %s", 742 t, 743 k, 744 err) 745 } 746 } 747 748 // If we have provisioners, then parse those out 749 var provisioners []*Provisioner 750 if os := listVal.Filter("provisioner"); len(os.Items) > 0 { 751 var err error 752 provisioners, err = loadProvisionersHcl(os, connInfo) 753 if err != nil { 754 return nil, fmt.Errorf( 755 "Error reading provisioners for %s[%s]: %s", 756 t, 757 k, 758 err) 759 } 760 } 761 762 // If we have a provider, then parse it out 763 var provider string 764 if o := listVal.Filter("provider"); len(o.Items) > 0 { 765 err := hcl.DecodeObject(&provider, o.Items[0].Val) 766 if err != nil { 767 return nil, fmt.Errorf( 768 "Error reading provider for %s[%s]: %s", 769 t, 770 k, 771 err) 772 } 773 } 774 775 // Check if the resource should be re-created before 776 // destroying the existing instance 777 var lifecycle ResourceLifecycle 778 if o := listVal.Filter("lifecycle"); len(o.Items) > 0 { 779 // Check for invalid keys 780 valid := []string{"create_before_destroy", "ignore_changes", "prevent_destroy"} 781 if err := checkHCLKeys(o.Items[0].Val, valid); err != nil { 782 return nil, multierror.Prefix(err, fmt.Sprintf( 783 "%s[%s]:", t, k)) 784 } 785 786 var raw map[string]interface{} 787 if err = hcl.DecodeObject(&raw, o.Items[0].Val); err != nil { 788 return nil, fmt.Errorf( 789 "Error parsing lifecycle for %s[%s]: %s", 790 t, 791 k, 792 err) 793 } 794 795 if err := mapstructure.WeakDecode(raw, &lifecycle); err != nil { 796 return nil, fmt.Errorf( 797 "Error parsing lifecycle for %s[%s]: %s", 798 t, 799 k, 800 err) 801 } 802 } 803 804 result = append(result, &Resource{ 805 Mode: ManagedResourceMode, 806 Name: k, 807 Type: t, 808 RawCount: countConfig, 809 RawConfig: rawConfig, 810 Provisioners: provisioners, 811 Provider: provider, 812 DependsOn: dependsOn, 813 Lifecycle: lifecycle, 814 }) 815 } 816 817 return result, nil 818 } 819 820 func loadProvisionersHcl(list *ast.ObjectList, connInfo map[string]interface{}) ([]*Provisioner, error) { 821 list = list.Children() 822 if len(list.Items) == 0 { 823 return nil, nil 824 } 825 826 // Go through each object and turn it into an actual result. 827 result := make([]*Provisioner, 0, len(list.Items)) 828 for _, item := range list.Items { 829 n := item.Keys[0].Token.Value().(string) 830 831 var listVal *ast.ObjectList 832 if ot, ok := item.Val.(*ast.ObjectType); ok { 833 listVal = ot.List 834 } else { 835 return nil, fmt.Errorf("provisioner '%s': should be an object", n) 836 } 837 838 var config map[string]interface{} 839 if err := hcl.DecodeObject(&config, item.Val); err != nil { 840 return nil, err 841 } 842 843 // Delete the "connection" section, handle separately 844 delete(config, "connection") 845 846 rawConfig, err := NewRawConfig(config) 847 if err != nil { 848 return nil, err 849 } 850 851 // Check if we have a provisioner-level connection 852 // block that overrides the resource-level 853 var subConnInfo map[string]interface{} 854 if o := listVal.Filter("connection"); len(o.Items) > 0 { 855 err := hcl.DecodeObject(&subConnInfo, o.Items[0].Val) 856 if err != nil { 857 return nil, err 858 } 859 } 860 861 // Inherit from the resource connInfo any keys 862 // that are not explicitly overriden. 863 if connInfo != nil && subConnInfo != nil { 864 for k, v := range connInfo { 865 if _, ok := subConnInfo[k]; !ok { 866 subConnInfo[k] = v 867 } 868 } 869 } else if subConnInfo == nil { 870 subConnInfo = connInfo 871 } 872 873 // Parse the connInfo 874 connRaw, err := NewRawConfig(subConnInfo) 875 if err != nil { 876 return nil, err 877 } 878 879 result = append(result, &Provisioner{ 880 Type: n, 881 RawConfig: rawConfig, 882 ConnInfo: connRaw, 883 }) 884 } 885 886 return result, nil 887 } 888 889 /* 890 func hclObjectMap(os *hclobj.Object) map[string]ast.ListNode { 891 objects := make(map[string][]*hclobj.Object) 892 893 for _, o := range os.Elem(false) { 894 for _, elem := range o.Elem(true) { 895 val, ok := objects[elem.Key] 896 if !ok { 897 val = make([]*hclobj.Object, 0, 1) 898 } 899 900 val = append(val, elem) 901 objects[elem.Key] = val 902 } 903 } 904 905 return objects 906 } 907 */ 908 909 func checkHCLKeys(node ast.Node, valid []string) error { 910 var list *ast.ObjectList 911 switch n := node.(type) { 912 case *ast.ObjectList: 913 list = n 914 case *ast.ObjectType: 915 list = n.List 916 default: 917 return fmt.Errorf("cannot check HCL keys of type %T", n) 918 } 919 920 validMap := make(map[string]struct{}, len(valid)) 921 for _, v := range valid { 922 validMap[v] = struct{}{} 923 } 924 925 var result error 926 for _, item := range list.Items { 927 key := item.Keys[0].Token.Value().(string) 928 if _, ok := validMap[key]; !ok { 929 result = multierror.Append(result, fmt.Errorf( 930 "invalid key: %s", key)) 931 } 932 } 933 934 return result 935 } 936 937 // unwrapHCLObjectKeysFromJSON cleans up an edge case that can occur when 938 // parsing JSON as input: if we're parsing JSON then directly nested 939 // items will show up as additional "keys". 940 // 941 // For objects that expect a fixed number of keys, this breaks the 942 // decoding process. This function unwraps the object into what it would've 943 // looked like if it came directly from HCL by specifying the number of keys 944 // you expect. 945 // 946 // Example: 947 // 948 // { "foo": { "baz": {} } } 949 // 950 // Will show up with Keys being: []string{"foo", "baz"} 951 // when we really just want the first two. This function will fix this. 952 func unwrapHCLObjectKeysFromJSON(item *ast.ObjectItem, depth int) { 953 if len(item.Keys) > depth && item.Keys[0].Token.JSON { 954 for len(item.Keys) > depth { 955 // Pop off the last key 956 n := len(item.Keys) 957 key := item.Keys[n-1] 958 item.Keys[n-1] = nil 959 item.Keys = item.Keys[:n-1] 960 961 // Wrap our value in a list 962 item.Val = &ast.ObjectType{ 963 List: &ast.ObjectList{ 964 Items: []*ast.ObjectItem{ 965 &ast.ObjectItem{ 966 Keys: []*ast.ObjectKey{key}, 967 Val: item.Val, 968 }, 969 }, 970 }, 971 } 972 } 973 } 974 }