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