github.com/ns1/terraform@v0.7.10-0.20161109153551-8949419bef40/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 "variable": struct{}{}, 29 } 30 31 // Top-level item should be the object list 32 list, ok := t.Root.Node.(*ast.ObjectList) 33 if !ok { 34 return nil, fmt.Errorf("error parsing: file doesn't contain a root object") 35 } 36 37 // Start building up the actual configuration. 38 config := new(Config) 39 40 // Build the variables 41 if vars := list.Filter("variable"); len(vars.Items) > 0 { 42 var err error 43 config.Variables, err = loadVariablesHcl(vars) 44 if err != nil { 45 return nil, err 46 } 47 } 48 49 // Get Atlas configuration 50 if atlas := list.Filter("atlas"); len(atlas.Items) > 0 { 51 var err error 52 config.Atlas, err = loadAtlasHcl(atlas) 53 if err != nil { 54 return nil, err 55 } 56 } 57 58 // Build the modules 59 if modules := list.Filter("module"); len(modules.Items) > 0 { 60 var err error 61 config.Modules, err = loadModulesHcl(modules) 62 if err != nil { 63 return nil, err 64 } 65 } 66 67 // Build the provider configs 68 if providers := list.Filter("provider"); len(providers.Items) > 0 { 69 var err error 70 config.ProviderConfigs, err = loadProvidersHcl(providers) 71 if err != nil { 72 return nil, err 73 } 74 } 75 76 // Build the resources 77 { 78 var err error 79 managedResourceConfigs := list.Filter("resource") 80 dataResourceConfigs := list.Filter("data") 81 82 config.Resources = make( 83 []*Resource, 0, 84 len(managedResourceConfigs.Items)+len(dataResourceConfigs.Items), 85 ) 86 87 managedResources, err := loadManagedResourcesHcl(managedResourceConfigs) 88 if err != nil { 89 return nil, err 90 } 91 dataResources, err := loadDataResourcesHcl(dataResourceConfigs) 92 if err != nil { 93 return nil, err 94 } 95 96 config.Resources = append(config.Resources, dataResources...) 97 config.Resources = append(config.Resources, managedResources...) 98 } 99 100 // Build the outputs 101 if outputs := list.Filter("output"); len(outputs.Items) > 0 { 102 var err error 103 config.Outputs, err = loadOutputsHcl(outputs) 104 if err != nil { 105 return nil, err 106 } 107 } 108 109 // Check for invalid keys 110 for _, item := range list.Items { 111 if len(item.Keys) == 0 { 112 // Not sure how this would happen, but let's avoid a panic 113 continue 114 } 115 116 k := item.Keys[0].Token.Value().(string) 117 if _, ok := validKeys[k]; ok { 118 continue 119 } 120 121 config.unknownKeys = append(config.unknownKeys, k) 122 } 123 124 return config, nil 125 } 126 127 // loadFileHcl is a fileLoaderFunc that knows how to read HCL 128 // files and turn them into hclConfigurables. 129 func loadFileHcl(root string) (configurable, []string, error) { 130 // Read the HCL file and prepare for parsing 131 d, err := ioutil.ReadFile(root) 132 if err != nil { 133 return nil, nil, fmt.Errorf( 134 "Error reading %s: %s", root, err) 135 } 136 137 // Parse it 138 hclRoot, err := hcl.Parse(string(d)) 139 if err != nil { 140 return nil, nil, fmt.Errorf( 141 "Error parsing %s: %s", root, err) 142 } 143 144 // Start building the result 145 result := &hclConfigurable{ 146 File: root, 147 Root: hclRoot, 148 } 149 150 // Dive in, find the imports. This is disabled for now since 151 // imports were removed prior to Terraform 0.1. The code is 152 // remaining here commented for historical purposes. 153 /* 154 imports := obj.Get("import") 155 if imports == nil { 156 result.Object.Ref() 157 return result, nil, nil 158 } 159 160 if imports.Type() != libucl.ObjectTypeString { 161 imports.Close() 162 163 return nil, nil, fmt.Errorf( 164 "Error in %s: all 'import' declarations should be in the format\n"+ 165 "`import \"foo\"` (Got type %s)", 166 root, 167 imports.Type()) 168 } 169 170 // Gather all the import paths 171 importPaths := make([]string, 0, imports.Len()) 172 iter := imports.Iterate(false) 173 for imp := iter.Next(); imp != nil; imp = iter.Next() { 174 path := imp.ToString() 175 if !filepath.IsAbs(path) { 176 // Relative paths are relative to the Terraform file itself 177 dir := filepath.Dir(root) 178 path = filepath.Join(dir, path) 179 } 180 181 importPaths = append(importPaths, path) 182 imp.Close() 183 } 184 iter.Close() 185 imports.Close() 186 187 result.Object.Ref() 188 */ 189 190 return result, nil, nil 191 } 192 193 // Given a handle to a HCL object, this transforms it into the Atlas 194 // configuration. 195 func loadAtlasHcl(list *ast.ObjectList) (*AtlasConfig, error) { 196 if len(list.Items) > 1 { 197 return nil, fmt.Errorf("only one 'atlas' block allowed") 198 } 199 200 // Get our one item 201 item := list.Items[0] 202 203 var config AtlasConfig 204 if err := hcl.DecodeObject(&config, item.Val); err != nil { 205 return nil, fmt.Errorf( 206 "Error reading atlas config: %s", 207 err) 208 } 209 210 return &config, nil 211 } 212 213 // Given a handle to a HCL object, this recurses into the structure 214 // and pulls out a list of modules. 215 // 216 // The resulting modules may not be unique, but each module 217 // represents exactly one module definition in the HCL configuration. 218 // We leave it up to another pass to merge them together. 219 func loadModulesHcl(list *ast.ObjectList) ([]*Module, error) { 220 list = list.Children() 221 if len(list.Items) == 0 { 222 return nil, nil 223 } 224 225 // Where all the results will go 226 var result []*Module 227 228 // Now go over all the types and their children in order to get 229 // all of the actual resources. 230 for _, item := range list.Items { 231 k := item.Keys[0].Token.Value().(string) 232 233 var listVal *ast.ObjectList 234 if ot, ok := item.Val.(*ast.ObjectType); ok { 235 listVal = ot.List 236 } else { 237 return nil, fmt.Errorf("module '%s': should be an object", k) 238 } 239 240 var config map[string]interface{} 241 if err := hcl.DecodeObject(&config, item.Val); err != nil { 242 return nil, fmt.Errorf( 243 "Error reading config for %s: %s", 244 k, 245 err) 246 } 247 248 // Remove the fields we handle specially 249 delete(config, "source") 250 251 rawConfig, err := NewRawConfig(config) 252 if err != nil { 253 return nil, fmt.Errorf( 254 "Error reading config for %s: %s", 255 k, 256 err) 257 } 258 259 // If we have a count, then figure it out 260 var source string 261 if o := listVal.Filter("source"); len(o.Items) > 0 { 262 err = hcl.DecodeObject(&source, o.Items[0].Val) 263 if err != nil { 264 return nil, fmt.Errorf( 265 "Error parsing source for %s: %s", 266 k, 267 err) 268 } 269 } 270 271 result = append(result, &Module{ 272 Name: k, 273 Source: source, 274 RawConfig: rawConfig, 275 }) 276 } 277 278 return result, nil 279 } 280 281 // LoadOutputsHcl recurses into the given HCL object and turns 282 // it into a mapping of outputs. 283 func loadOutputsHcl(list *ast.ObjectList) ([]*Output, error) { 284 list = list.Children() 285 if len(list.Items) == 0 { 286 return nil, fmt.Errorf( 287 "'output' must be followed by exactly one string: a name") 288 } 289 290 // Go through each object and turn it into an actual result. 291 result := make([]*Output, 0, len(list.Items)) 292 for _, item := range list.Items { 293 n := item.Keys[0].Token.Value().(string) 294 295 var config map[string]interface{} 296 if err := hcl.DecodeObject(&config, item.Val); err != nil { 297 return nil, err 298 } 299 300 rawConfig, err := NewRawConfig(config) 301 if err != nil { 302 return nil, fmt.Errorf( 303 "Error reading config for output %s: %s", 304 n, 305 err) 306 } 307 308 result = append(result, &Output{ 309 Name: n, 310 RawConfig: rawConfig, 311 }) 312 } 313 314 return result, nil 315 } 316 317 // LoadVariablesHcl recurses into the given HCL object and turns 318 // it into a list of variables. 319 func loadVariablesHcl(list *ast.ObjectList) ([]*Variable, error) { 320 list = list.Children() 321 if len(list.Items) == 0 { 322 return nil, fmt.Errorf( 323 "'variable' must be followed by exactly one strings: a name") 324 } 325 326 // hclVariable is the structure each variable is decoded into 327 type hclVariable struct { 328 DeclaredType string `hcl:"type"` 329 Default interface{} 330 Description string 331 Fields []string `hcl:",decodedFields"` 332 } 333 334 // Go through each object and turn it into an actual result. 335 result := make([]*Variable, 0, len(list.Items)) 336 for _, item := range list.Items { 337 // Clean up items from JSON 338 unwrapHCLObjectKeysFromJSON(item, 1) 339 340 // Verify the keys 341 if len(item.Keys) != 1 { 342 return nil, fmt.Errorf( 343 "position %s: 'variable' must be followed by exactly one strings: a name", 344 item.Pos()) 345 } 346 347 n := item.Keys[0].Token.Value().(string) 348 349 /* 350 // TODO: catch extra fields 351 // Decode into raw map[string]interface{} so we know ALL fields 352 var config map[string]interface{} 353 if err := hcl.DecodeObject(&config, item.Val); err != nil { 354 return nil, err 355 } 356 */ 357 358 // Decode into hclVariable to get typed values 359 var hclVar hclVariable 360 if err := hcl.DecodeObject(&hclVar, item.Val); err != nil { 361 return nil, err 362 } 363 364 // Defaults turn into a slice of map[string]interface{} and 365 // we need to make sure to convert that down into the 366 // proper type for Config. 367 if ms, ok := hclVar.Default.([]map[string]interface{}); ok { 368 def := make(map[string]interface{}) 369 for _, m := range ms { 370 for k, v := range m { 371 def[k] = v 372 } 373 } 374 375 hclVar.Default = def 376 } 377 378 // Build the new variable and do some basic validation 379 newVar := &Variable{ 380 Name: n, 381 DeclaredType: hclVar.DeclaredType, 382 Default: hclVar.Default, 383 Description: hclVar.Description, 384 } 385 if err := newVar.ValidateTypeAndDefault(); err != nil { 386 return nil, err 387 } 388 389 result = append(result, newVar) 390 } 391 392 return result, nil 393 } 394 395 // LoadProvidersHcl recurses into the given HCL object and turns 396 // it into a mapping of provider configs. 397 func loadProvidersHcl(list *ast.ObjectList) ([]*ProviderConfig, error) { 398 list = list.Children() 399 if len(list.Items) == 0 { 400 return nil, nil 401 } 402 403 // Go through each object and turn it into an actual result. 404 result := make([]*ProviderConfig, 0, len(list.Items)) 405 for _, item := range list.Items { 406 n := item.Keys[0].Token.Value().(string) 407 408 var listVal *ast.ObjectList 409 if ot, ok := item.Val.(*ast.ObjectType); ok { 410 listVal = ot.List 411 } else { 412 return nil, fmt.Errorf("module '%s': should be an object", n) 413 } 414 415 var config map[string]interface{} 416 if err := hcl.DecodeObject(&config, item.Val); err != nil { 417 return nil, err 418 } 419 420 delete(config, "alias") 421 422 rawConfig, err := NewRawConfig(config) 423 if err != nil { 424 return nil, fmt.Errorf( 425 "Error reading config for provider config %s: %s", 426 n, 427 err) 428 } 429 430 // If we have an alias field, then add those in 431 var alias string 432 if a := listVal.Filter("alias"); len(a.Items) > 0 { 433 err := hcl.DecodeObject(&alias, a.Items[0].Val) 434 if err != nil { 435 return nil, fmt.Errorf( 436 "Error reading alias for provider[%s]: %s", 437 n, 438 err) 439 } 440 } 441 442 result = append(result, &ProviderConfig{ 443 Name: n, 444 Alias: alias, 445 RawConfig: rawConfig, 446 }) 447 } 448 449 return result, nil 450 } 451 452 // Given a handle to a HCL object, this recurses into the structure 453 // and pulls out a list of data sources. 454 // 455 // The resulting data sources may not be unique, but each one 456 // represents exactly one data definition in the HCL configuration. 457 // We leave it up to another pass to merge them together. 458 func loadDataResourcesHcl(list *ast.ObjectList) ([]*Resource, error) { 459 list = list.Children() 460 if len(list.Items) == 0 { 461 return nil, nil 462 } 463 464 // Where all the results will go 465 var result []*Resource 466 467 // Now go over all the types and their children in order to get 468 // all of the actual resources. 469 for _, item := range list.Items { 470 if len(item.Keys) != 2 { 471 return nil, fmt.Errorf( 472 "position %s: 'data' must be followed by exactly two strings: a type and a name", 473 item.Pos()) 474 } 475 476 t := item.Keys[0].Token.Value().(string) 477 k := item.Keys[1].Token.Value().(string) 478 479 var listVal *ast.ObjectList 480 if ot, ok := item.Val.(*ast.ObjectType); ok { 481 listVal = ot.List 482 } else { 483 return nil, fmt.Errorf("data sources %s[%s]: should be an object", t, k) 484 } 485 486 var config map[string]interface{} 487 if err := hcl.DecodeObject(&config, item.Val); err != nil { 488 return nil, fmt.Errorf( 489 "Error reading config for %s[%s]: %s", 490 t, 491 k, 492 err) 493 } 494 495 // Remove the fields we handle specially 496 delete(config, "depends_on") 497 delete(config, "provider") 498 delete(config, "count") 499 500 rawConfig, err := NewRawConfig(config) 501 if err != nil { 502 return nil, fmt.Errorf( 503 "Error reading config for %s[%s]: %s", 504 t, 505 k, 506 err) 507 } 508 509 // If we have a count, then figure it out 510 var count string = "1" 511 if o := listVal.Filter("count"); len(o.Items) > 0 { 512 err = hcl.DecodeObject(&count, o.Items[0].Val) 513 if err != nil { 514 return nil, fmt.Errorf( 515 "Error parsing count for %s[%s]: %s", 516 t, 517 k, 518 err) 519 } 520 } 521 countConfig, err := NewRawConfig(map[string]interface{}{ 522 "count": count, 523 }) 524 if err != nil { 525 return nil, err 526 } 527 countConfig.Key = "count" 528 529 // If we have depends fields, then add those in 530 var dependsOn []string 531 if o := listVal.Filter("depends_on"); len(o.Items) > 0 { 532 err := hcl.DecodeObject(&dependsOn, o.Items[0].Val) 533 if err != nil { 534 return nil, fmt.Errorf( 535 "Error reading depends_on for %s[%s]: %s", 536 t, 537 k, 538 err) 539 } 540 } 541 542 // If we have a provider, then parse it out 543 var provider string 544 if o := listVal.Filter("provider"); len(o.Items) > 0 { 545 err := hcl.DecodeObject(&provider, o.Items[0].Val) 546 if err != nil { 547 return nil, fmt.Errorf( 548 "Error reading provider for %s[%s]: %s", 549 t, 550 k, 551 err) 552 } 553 } 554 555 result = append(result, &Resource{ 556 Mode: DataResourceMode, 557 Name: k, 558 Type: t, 559 RawCount: countConfig, 560 RawConfig: rawConfig, 561 Provider: provider, 562 Provisioners: []*Provisioner{}, 563 DependsOn: dependsOn, 564 Lifecycle: ResourceLifecycle{}, 565 }) 566 } 567 568 return result, nil 569 } 570 571 // Given a handle to a HCL object, this recurses into the structure 572 // and pulls out a list of managed resources. 573 // 574 // The resulting resources may not be unique, but each resource 575 // represents exactly one "resource" block in the HCL configuration. 576 // We leave it up to another pass to merge them together. 577 func loadManagedResourcesHcl(list *ast.ObjectList) ([]*Resource, error) { 578 list = list.Children() 579 if len(list.Items) == 0 { 580 return nil, nil 581 } 582 583 // Where all the results will go 584 var result []*Resource 585 586 // Now go over all the types and their children in order to get 587 // all of the actual resources. 588 for _, item := range list.Items { 589 // GH-4385: We detect a pure provisioner resource and give the user 590 // an error about how to do it cleanly. 591 if len(item.Keys) == 4 && item.Keys[2].Token.Value().(string) == "provisioner" { 592 return nil, fmt.Errorf( 593 "position %s: provisioners in a resource should be wrapped in a list\n\n"+ 594 "Example: \"provisioner\": [ { \"local-exec\": ... } ]", 595 item.Pos()) 596 } 597 598 // Fix up JSON input 599 unwrapHCLObjectKeysFromJSON(item, 2) 600 601 if len(item.Keys) != 2 { 602 return nil, fmt.Errorf( 603 "position %s: resource must be followed by exactly two strings, a type and a name", 604 item.Pos()) 605 } 606 607 t := item.Keys[0].Token.Value().(string) 608 k := item.Keys[1].Token.Value().(string) 609 610 var listVal *ast.ObjectList 611 if ot, ok := item.Val.(*ast.ObjectType); ok { 612 listVal = ot.List 613 } else { 614 return nil, fmt.Errorf("resources %s[%s]: should be an object", t, k) 615 } 616 617 var config map[string]interface{} 618 if err := hcl.DecodeObject(&config, item.Val); err != nil { 619 return nil, fmt.Errorf( 620 "Error reading config for %s[%s]: %s", 621 t, 622 k, 623 err) 624 } 625 626 // Remove the fields we handle specially 627 delete(config, "connection") 628 delete(config, "count") 629 delete(config, "depends_on") 630 delete(config, "provisioner") 631 delete(config, "provider") 632 delete(config, "lifecycle") 633 634 rawConfig, err := NewRawConfig(config) 635 if err != nil { 636 return nil, fmt.Errorf( 637 "Error reading config for %s[%s]: %s", 638 t, 639 k, 640 err) 641 } 642 643 // If we have a count, then figure it out 644 var count string = "1" 645 if o := listVal.Filter("count"); len(o.Items) > 0 { 646 err = hcl.DecodeObject(&count, o.Items[0].Val) 647 if err != nil { 648 return nil, fmt.Errorf( 649 "Error parsing count for %s[%s]: %s", 650 t, 651 k, 652 err) 653 } 654 } 655 countConfig, err := NewRawConfig(map[string]interface{}{ 656 "count": count, 657 }) 658 if err != nil { 659 return nil, err 660 } 661 countConfig.Key = "count" 662 663 // If we have depends fields, then add those in 664 var dependsOn []string 665 if o := listVal.Filter("depends_on"); len(o.Items) > 0 { 666 err := hcl.DecodeObject(&dependsOn, o.Items[0].Val) 667 if err != nil { 668 return nil, fmt.Errorf( 669 "Error reading depends_on for %s[%s]: %s", 670 t, 671 k, 672 err) 673 } 674 } 675 676 // If we have connection info, then parse those out 677 var connInfo map[string]interface{} 678 if o := listVal.Filter("connection"); len(o.Items) > 0 { 679 err := hcl.DecodeObject(&connInfo, o.Items[0].Val) 680 if err != nil { 681 return nil, fmt.Errorf( 682 "Error reading connection info for %s[%s]: %s", 683 t, 684 k, 685 err) 686 } 687 } 688 689 // If we have provisioners, then parse those out 690 var provisioners []*Provisioner 691 if os := listVal.Filter("provisioner"); len(os.Items) > 0 { 692 var err error 693 provisioners, err = loadProvisionersHcl(os, connInfo) 694 if err != nil { 695 return nil, fmt.Errorf( 696 "Error reading provisioners for %s[%s]: %s", 697 t, 698 k, 699 err) 700 } 701 } 702 703 // If we have a provider, then parse it out 704 var provider string 705 if o := listVal.Filter("provider"); len(o.Items) > 0 { 706 err := hcl.DecodeObject(&provider, o.Items[0].Val) 707 if err != nil { 708 return nil, fmt.Errorf( 709 "Error reading provider for %s[%s]: %s", 710 t, 711 k, 712 err) 713 } 714 } 715 716 // Check if the resource should be re-created before 717 // destroying the existing instance 718 var lifecycle ResourceLifecycle 719 if o := listVal.Filter("lifecycle"); len(o.Items) > 0 { 720 // Check for invalid keys 721 valid := []string{"create_before_destroy", "ignore_changes", "prevent_destroy"} 722 if err := checkHCLKeys(o.Items[0].Val, valid); err != nil { 723 return nil, multierror.Prefix(err, fmt.Sprintf( 724 "%s[%s]:", t, k)) 725 } 726 727 var raw map[string]interface{} 728 if err = hcl.DecodeObject(&raw, o.Items[0].Val); err != nil { 729 return nil, fmt.Errorf( 730 "Error parsing lifecycle for %s[%s]: %s", 731 t, 732 k, 733 err) 734 } 735 736 if err := mapstructure.WeakDecode(raw, &lifecycle); err != nil { 737 return nil, fmt.Errorf( 738 "Error parsing lifecycle for %s[%s]: %s", 739 t, 740 k, 741 err) 742 } 743 } 744 745 result = append(result, &Resource{ 746 Mode: ManagedResourceMode, 747 Name: k, 748 Type: t, 749 RawCount: countConfig, 750 RawConfig: rawConfig, 751 Provisioners: provisioners, 752 Provider: provider, 753 DependsOn: dependsOn, 754 Lifecycle: lifecycle, 755 }) 756 } 757 758 return result, nil 759 } 760 761 func loadProvisionersHcl(list *ast.ObjectList, connInfo map[string]interface{}) ([]*Provisioner, error) { 762 list = list.Children() 763 if len(list.Items) == 0 { 764 return nil, nil 765 } 766 767 // Go through each object and turn it into an actual result. 768 result := make([]*Provisioner, 0, len(list.Items)) 769 for _, item := range list.Items { 770 n := item.Keys[0].Token.Value().(string) 771 772 var listVal *ast.ObjectList 773 if ot, ok := item.Val.(*ast.ObjectType); ok { 774 listVal = ot.List 775 } else { 776 return nil, fmt.Errorf("provisioner '%s': should be an object", n) 777 } 778 779 var config map[string]interface{} 780 if err := hcl.DecodeObject(&config, item.Val); err != nil { 781 return nil, err 782 } 783 784 // Delete the "connection" section, handle separately 785 delete(config, "connection") 786 787 rawConfig, err := NewRawConfig(config) 788 if err != nil { 789 return nil, err 790 } 791 792 // Check if we have a provisioner-level connection 793 // block that overrides the resource-level 794 var subConnInfo map[string]interface{} 795 if o := listVal.Filter("connection"); len(o.Items) > 0 { 796 err := hcl.DecodeObject(&subConnInfo, o.Items[0].Val) 797 if err != nil { 798 return nil, err 799 } 800 } 801 802 // Inherit from the resource connInfo any keys 803 // that are not explicitly overriden. 804 if connInfo != nil && subConnInfo != nil { 805 for k, v := range connInfo { 806 if _, ok := subConnInfo[k]; !ok { 807 subConnInfo[k] = v 808 } 809 } 810 } else if subConnInfo == nil { 811 subConnInfo = connInfo 812 } 813 814 // Parse the connInfo 815 connRaw, err := NewRawConfig(subConnInfo) 816 if err != nil { 817 return nil, err 818 } 819 820 result = append(result, &Provisioner{ 821 Type: n, 822 RawConfig: rawConfig, 823 ConnInfo: connRaw, 824 }) 825 } 826 827 return result, nil 828 } 829 830 /* 831 func hclObjectMap(os *hclobj.Object) map[string]ast.ListNode { 832 objects := make(map[string][]*hclobj.Object) 833 834 for _, o := range os.Elem(false) { 835 for _, elem := range o.Elem(true) { 836 val, ok := objects[elem.Key] 837 if !ok { 838 val = make([]*hclobj.Object, 0, 1) 839 } 840 841 val = append(val, elem) 842 objects[elem.Key] = val 843 } 844 } 845 846 return objects 847 } 848 */ 849 850 func checkHCLKeys(node ast.Node, valid []string) error { 851 var list *ast.ObjectList 852 switch n := node.(type) { 853 case *ast.ObjectList: 854 list = n 855 case *ast.ObjectType: 856 list = n.List 857 default: 858 return fmt.Errorf("cannot check HCL keys of type %T", n) 859 } 860 861 validMap := make(map[string]struct{}, len(valid)) 862 for _, v := range valid { 863 validMap[v] = struct{}{} 864 } 865 866 var result error 867 for _, item := range list.Items { 868 key := item.Keys[0].Token.Value().(string) 869 if _, ok := validMap[key]; !ok { 870 result = multierror.Append(result, fmt.Errorf( 871 "invalid key: %s", key)) 872 } 873 } 874 875 return result 876 } 877 878 // unwrapHCLObjectKeysFromJSON cleans up an edge case that can occur when 879 // parsing JSON as input: if we're parsing JSON then directly nested 880 // items will show up as additional "keys". 881 // 882 // For objects that expect a fixed number of keys, this breaks the 883 // decoding process. This function unwraps the object into what it would've 884 // looked like if it came directly from HCL by specifying the number of keys 885 // you expect. 886 // 887 // Example: 888 // 889 // { "foo": { "baz": {} } } 890 // 891 // Will show up with Keys being: []string{"foo", "baz"} 892 // when we really just want the first two. This function will fix this. 893 func unwrapHCLObjectKeysFromJSON(item *ast.ObjectItem, depth int) { 894 if len(item.Keys) > depth && item.Keys[0].Token.JSON { 895 for len(item.Keys) > depth { 896 // Pop off the last key 897 n := len(item.Keys) 898 key := item.Keys[n-1] 899 item.Keys[n-1] = nil 900 item.Keys = item.Keys[:n-1] 901 902 // Wrap our value in a list 903 item.Val = &ast.ObjectType{ 904 List: &ast.ObjectList{ 905 Items: []*ast.ObjectItem{ 906 &ast.ObjectItem{ 907 Keys: []*ast.ObjectKey{key}, 908 Val: item.Val, 909 }, 910 }, 911 }, 912 } 913 } 914 } 915 }