github.com/mapuri/terraform@v0.7.6-0.20161012203214-7e0408293f97/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 delete(config, "count") 461 462 rawConfig, err := NewRawConfig(config) 463 if err != nil { 464 return nil, fmt.Errorf( 465 "Error reading config for %s[%s]: %s", 466 t, 467 k, 468 err) 469 } 470 471 // If we have a count, then figure it out 472 var count string = "1" 473 if o := listVal.Filter("count"); len(o.Items) > 0 { 474 err = hcl.DecodeObject(&count, o.Items[0].Val) 475 if err != nil { 476 return nil, fmt.Errorf( 477 "Error parsing count for %s[%s]: %s", 478 t, 479 k, 480 err) 481 } 482 } 483 countConfig, err := NewRawConfig(map[string]interface{}{ 484 "count": count, 485 }) 486 if err != nil { 487 return nil, err 488 } 489 countConfig.Key = "count" 490 491 // If we have depends fields, then add those in 492 var dependsOn []string 493 if o := listVal.Filter("depends_on"); len(o.Items) > 0 { 494 err := hcl.DecodeObject(&dependsOn, o.Items[0].Val) 495 if err != nil { 496 return nil, fmt.Errorf( 497 "Error reading depends_on for %s[%s]: %s", 498 t, 499 k, 500 err) 501 } 502 } 503 504 // If we have a provider, then parse it out 505 var provider string 506 if o := listVal.Filter("provider"); len(o.Items) > 0 { 507 err := hcl.DecodeObject(&provider, o.Items[0].Val) 508 if err != nil { 509 return nil, fmt.Errorf( 510 "Error reading provider for %s[%s]: %s", 511 t, 512 k, 513 err) 514 } 515 } 516 517 result = append(result, &Resource{ 518 Mode: DataResourceMode, 519 Name: k, 520 Type: t, 521 RawCount: countConfig, 522 RawConfig: rawConfig, 523 Provider: provider, 524 Provisioners: []*Provisioner{}, 525 DependsOn: dependsOn, 526 Lifecycle: ResourceLifecycle{}, 527 }) 528 } 529 530 return result, nil 531 } 532 533 // Given a handle to a HCL object, this recurses into the structure 534 // and pulls out a list of managed resources. 535 // 536 // The resulting resources may not be unique, but each resource 537 // represents exactly one "resource" block in the HCL configuration. 538 // We leave it up to another pass to merge them together. 539 func loadManagedResourcesHcl(list *ast.ObjectList) ([]*Resource, error) { 540 list = list.Children() 541 if len(list.Items) == 0 { 542 return nil, nil 543 } 544 545 // Where all the results will go 546 var result []*Resource 547 548 // Now go over all the types and their children in order to get 549 // all of the actual resources. 550 for _, item := range list.Items { 551 // GH-4385: We detect a pure provisioner resource and give the user 552 // an error about how to do it cleanly. 553 if len(item.Keys) == 4 && item.Keys[2].Token.Value().(string) == "provisioner" { 554 return nil, fmt.Errorf( 555 "position %s: provisioners in a resource should be wrapped in a list\n\n"+ 556 "Example: \"provisioner\": [ { \"local-exec\": ... } ]", 557 item.Pos()) 558 } 559 560 // HCL special case: if we're parsing JSON then directly nested 561 // items will show up as additional "keys". We need to unwrap them 562 // since we expect only two keys. Example: 563 // 564 // { "foo": { "bar": { "baz": {} } } } 565 // 566 // Will show up with Keys being: []string{"foo", "bar", "baz"} 567 // when we really just want the first two. To fix this we unwrap 568 // them into the right value. 569 if len(item.Keys) > 2 && item.Keys[0].Token.JSON { 570 for len(item.Keys) > 2 { 571 // Pop off the last key 572 n := len(item.Keys) 573 key := item.Keys[n-1] 574 item.Keys[n-1] = nil 575 item.Keys = item.Keys[:n-1] 576 577 // Wrap our value in a list 578 item.Val = &ast.ObjectType{ 579 List: &ast.ObjectList{ 580 Items: []*ast.ObjectItem{ 581 &ast.ObjectItem{ 582 Keys: []*ast.ObjectKey{key}, 583 Val: item.Val, 584 }, 585 }, 586 }, 587 } 588 } 589 } 590 591 if len(item.Keys) != 2 { 592 return nil, fmt.Errorf( 593 "position %s: resource must be followed by exactly two strings, a type and a name", 594 item.Pos()) 595 } 596 597 t := item.Keys[0].Token.Value().(string) 598 k := item.Keys[1].Token.Value().(string) 599 600 var listVal *ast.ObjectList 601 if ot, ok := item.Val.(*ast.ObjectType); ok { 602 listVal = ot.List 603 } else { 604 return nil, fmt.Errorf("resources %s[%s]: should be an object", t, k) 605 } 606 607 var config map[string]interface{} 608 if err := hcl.DecodeObject(&config, item.Val); err != nil { 609 return nil, fmt.Errorf( 610 "Error reading config for %s[%s]: %s", 611 t, 612 k, 613 err) 614 } 615 616 // Remove the fields we handle specially 617 delete(config, "connection") 618 delete(config, "count") 619 delete(config, "depends_on") 620 delete(config, "provisioner") 621 delete(config, "provider") 622 delete(config, "lifecycle") 623 624 rawConfig, err := NewRawConfig(config) 625 if err != nil { 626 return nil, fmt.Errorf( 627 "Error reading config for %s[%s]: %s", 628 t, 629 k, 630 err) 631 } 632 633 // If we have a count, then figure it out 634 var count string = "1" 635 if o := listVal.Filter("count"); len(o.Items) > 0 { 636 err = hcl.DecodeObject(&count, o.Items[0].Val) 637 if err != nil { 638 return nil, fmt.Errorf( 639 "Error parsing count for %s[%s]: %s", 640 t, 641 k, 642 err) 643 } 644 } 645 countConfig, err := NewRawConfig(map[string]interface{}{ 646 "count": count, 647 }) 648 if err != nil { 649 return nil, err 650 } 651 countConfig.Key = "count" 652 653 // If we have depends fields, then add those in 654 var dependsOn []string 655 if o := listVal.Filter("depends_on"); len(o.Items) > 0 { 656 err := hcl.DecodeObject(&dependsOn, o.Items[0].Val) 657 if err != nil { 658 return nil, fmt.Errorf( 659 "Error reading depends_on for %s[%s]: %s", 660 t, 661 k, 662 err) 663 } 664 } 665 666 // If we have connection info, then parse those out 667 var connInfo map[string]interface{} 668 if o := listVal.Filter("connection"); len(o.Items) > 0 { 669 err := hcl.DecodeObject(&connInfo, o.Items[0].Val) 670 if err != nil { 671 return nil, fmt.Errorf( 672 "Error reading connection info for %s[%s]: %s", 673 t, 674 k, 675 err) 676 } 677 } 678 679 // If we have provisioners, then parse those out 680 var provisioners []*Provisioner 681 if os := listVal.Filter("provisioner"); len(os.Items) > 0 { 682 var err error 683 provisioners, err = loadProvisionersHcl(os, connInfo) 684 if err != nil { 685 return nil, fmt.Errorf( 686 "Error reading provisioners for %s[%s]: %s", 687 t, 688 k, 689 err) 690 } 691 } 692 693 // If we have a provider, then parse it out 694 var provider string 695 if o := listVal.Filter("provider"); len(o.Items) > 0 { 696 err := hcl.DecodeObject(&provider, o.Items[0].Val) 697 if err != nil { 698 return nil, fmt.Errorf( 699 "Error reading provider for %s[%s]: %s", 700 t, 701 k, 702 err) 703 } 704 } 705 706 // Check if the resource should be re-created before 707 // destroying the existing instance 708 var lifecycle ResourceLifecycle 709 if o := listVal.Filter("lifecycle"); len(o.Items) > 0 { 710 // Check for invalid keys 711 valid := []string{"create_before_destroy", "ignore_changes", "prevent_destroy"} 712 if err := checkHCLKeys(o.Items[0].Val, valid); err != nil { 713 return nil, multierror.Prefix(err, fmt.Sprintf( 714 "%s[%s]:", t, k)) 715 } 716 717 var raw map[string]interface{} 718 if err = hcl.DecodeObject(&raw, o.Items[0].Val); err != nil { 719 return nil, fmt.Errorf( 720 "Error parsing lifecycle for %s[%s]: %s", 721 t, 722 k, 723 err) 724 } 725 726 if err := mapstructure.WeakDecode(raw, &lifecycle); err != nil { 727 return nil, fmt.Errorf( 728 "Error parsing lifecycle for %s[%s]: %s", 729 t, 730 k, 731 err) 732 } 733 } 734 735 result = append(result, &Resource{ 736 Mode: ManagedResourceMode, 737 Name: k, 738 Type: t, 739 RawCount: countConfig, 740 RawConfig: rawConfig, 741 Provisioners: provisioners, 742 Provider: provider, 743 DependsOn: dependsOn, 744 Lifecycle: lifecycle, 745 }) 746 } 747 748 return result, nil 749 } 750 751 func loadProvisionersHcl(list *ast.ObjectList, connInfo map[string]interface{}) ([]*Provisioner, error) { 752 list = list.Children() 753 if len(list.Items) == 0 { 754 return nil, nil 755 } 756 757 // Go through each object and turn it into an actual result. 758 result := make([]*Provisioner, 0, len(list.Items)) 759 for _, item := range list.Items { 760 n := item.Keys[0].Token.Value().(string) 761 762 var listVal *ast.ObjectList 763 if ot, ok := item.Val.(*ast.ObjectType); ok { 764 listVal = ot.List 765 } else { 766 return nil, fmt.Errorf("provisioner '%s': should be an object", n) 767 } 768 769 var config map[string]interface{} 770 if err := hcl.DecodeObject(&config, item.Val); err != nil { 771 return nil, err 772 } 773 774 // Delete the "connection" section, handle separately 775 delete(config, "connection") 776 777 rawConfig, err := NewRawConfig(config) 778 if err != nil { 779 return nil, err 780 } 781 782 // Check if we have a provisioner-level connection 783 // block that overrides the resource-level 784 var subConnInfo map[string]interface{} 785 if o := listVal.Filter("connection"); len(o.Items) > 0 { 786 err := hcl.DecodeObject(&subConnInfo, o.Items[0].Val) 787 if err != nil { 788 return nil, err 789 } 790 } 791 792 // Inherit from the resource connInfo any keys 793 // that are not explicitly overriden. 794 if connInfo != nil && subConnInfo != nil { 795 for k, v := range connInfo { 796 if _, ok := subConnInfo[k]; !ok { 797 subConnInfo[k] = v 798 } 799 } 800 } else if subConnInfo == nil { 801 subConnInfo = connInfo 802 } 803 804 // Parse the connInfo 805 connRaw, err := NewRawConfig(subConnInfo) 806 if err != nil { 807 return nil, err 808 } 809 810 result = append(result, &Provisioner{ 811 Type: n, 812 RawConfig: rawConfig, 813 ConnInfo: connRaw, 814 }) 815 } 816 817 return result, nil 818 } 819 820 /* 821 func hclObjectMap(os *hclobj.Object) map[string]ast.ListNode { 822 objects := make(map[string][]*hclobj.Object) 823 824 for _, o := range os.Elem(false) { 825 for _, elem := range o.Elem(true) { 826 val, ok := objects[elem.Key] 827 if !ok { 828 val = make([]*hclobj.Object, 0, 1) 829 } 830 831 val = append(val, elem) 832 objects[elem.Key] = val 833 } 834 } 835 836 return objects 837 } 838 */ 839 840 func checkHCLKeys(node ast.Node, valid []string) error { 841 var list *ast.ObjectList 842 switch n := node.(type) { 843 case *ast.ObjectList: 844 list = n 845 case *ast.ObjectType: 846 list = n.List 847 default: 848 return fmt.Errorf("cannot check HCL keys of type %T", n) 849 } 850 851 validMap := make(map[string]struct{}, len(valid)) 852 for _, v := range valid { 853 validMap[v] = struct{}{} 854 } 855 856 var result error 857 for _, item := range list.Items { 858 key := item.Keys[0].Token.Value().(string) 859 if _, ok := validMap[key]; !ok { 860 result = multierror.Append(result, fmt.Errorf( 861 "invalid key: %s", key)) 862 } 863 } 864 865 return result 866 }