7 "github.com/hashicorp/go-multierror"
8 "github.com/hashicorp/hcl"
9 "github.com/hashicorp/hcl/hcl/ast"
10 "github.com/mitchellh/mapstructure"
13 // hclConfigurable is an implementation of configurable that knows
14 // how to turn HCL configuration into a *Config object.
15 type hclConfigurable struct {
20 var ReservedResourceFields = []string{
29 var ReservedProviderFields = []string{
34 func (t *hclConfigurable) Config() (*Config, error) {
35 validKeys := map[string]struct{}{
40 "provider": struct{}{},
41 "resource": struct{}{},
42 "terraform": struct{}{},
43 "variable": struct{}{},
46 // Top-level item should be the object list
47 list, ok := t.Root.Node.(*ast.ObjectList)
49 return nil, fmt.Errorf("error parsing: file doesn't contain a root object")
52 // Start building up the actual configuration.
56 if o := list.Filter("terraform"); len(o.Items) > 0 {
58 config.Terraform, err = loadTerraformHcl(o)
64 // Build the variables
65 if vars := list.Filter("variable"); len(vars.Items) > 0 {
67 config.Variables, err = loadVariablesHcl(vars)
73 // Get Atlas configuration
74 if atlas := list.Filter("atlas"); len(atlas.Items) > 0 {
76 config.Atlas, err = loadAtlasHcl(atlas)
83 if modules := list.Filter("module"); len(modules.Items) > 0 {
85 config.Modules, err = loadModulesHcl(modules)
91 // Build the provider configs
92 if providers := list.Filter("provider"); len(providers.Items) > 0 {
94 config.ProviderConfigs, err = loadProvidersHcl(providers)
100 // Build the resources
103 managedResourceConfigs := list.Filter("resource")
104 dataResourceConfigs := list.Filter("data")
106 config.Resources = make(
108 len(managedResourceConfigs.Items)+len(dataResourceConfigs.Items),
111 managedResources, err := loadManagedResourcesHcl(managedResourceConfigs)
115 dataResources, err := loadDataResourcesHcl(dataResourceConfigs)
120 config.Resources = append(config.Resources, dataResources...)
121 config.Resources = append(config.Resources, managedResources...)
125 if outputs := list.Filter("output"); len(outputs.Items) > 0 {
127 config.Outputs, err = loadOutputsHcl(outputs)
133 // Check for invalid keys
134 for _, item := range list.Items {
135 if len(item.Keys) == 0 {
136 // Not sure how this would happen, but let's avoid a panic
140 k := item.Keys[0].Token.Value().(string)
141 if _, ok := validKeys[k]; ok {
145 config.unknownKeys = append(config.unknownKeys, k)
151 // loadFileHcl is a fileLoaderFunc that knows how to read HCL
152 // files and turn them into hclConfigurables.
153 func loadFileHcl(root string) (configurable, []string, error) {
154 // Read the HCL file and prepare for parsing
155 d, err := ioutil.ReadFile(root)
157 return nil, nil, fmt.Errorf(
158 "Error reading %s: %s", root, err)
162 hclRoot, err := hcl.Parse(string(d))
164 return nil, nil, fmt.Errorf(
165 "Error parsing %s: %s", root, err)
168 // Start building the result
169 result := &hclConfigurable{
174 // Dive in, find the imports. This is disabled for now since
175 // imports were removed prior to Terraform 0.1. The code is
176 // remaining here commented for historical purposes.
178 imports := obj.Get("import")
181 return result, nil, nil
184 if imports.Type() != libucl.ObjectTypeString {
187 return nil, nil, fmt.Errorf(
188 "Error in %s: all 'import' declarations should be in the format\n"+
189 "`import \"foo\"` (Got type %s)",
194 // Gather all the import paths
195 importPaths := make([]string, 0, imports.Len())
196 iter := imports.Iterate(false)
197 for imp := iter.Next(); imp != nil; imp = iter.Next() {
198 path := imp.ToString()
199 if !filepath.IsAbs(path) {
200 // Relative paths are relative to the Terraform file itself
201 dir := filepath.Dir(root)
202 path = filepath.Join(dir, path)
205 importPaths = append(importPaths, path)
214 return result, nil, nil
217 // Given a handle to a HCL object, this transforms it into the Terraform config
218 func loadTerraformHcl(list *ast.ObjectList) (*Terraform, error) {
219 if len(list.Items) > 1 {
220 return nil, fmt.Errorf("only one 'terraform' block allowed per module")
224 item := list.Items[0]
226 // This block should have an empty top level ObjectItem. If there are keys
227 // here, it's likely because we have a flattened JSON object, and we can
228 // lift this into a nested ObjectList to decode properly.
229 if len(item.Keys) > 0 {
230 item = &ast.ObjectItem{
231 Val: &ast.ObjectType{
232 List: &ast.ObjectList{
233 Items: []*ast.ObjectItem{item},
239 // We need the item value as an ObjectList
240 var listVal *ast.ObjectList
241 if ot, ok := item.Val.(*ast.ObjectType); ok {
244 return nil, fmt.Errorf("terraform block: should be an object")
247 // NOTE: We purposely don't validate unknown HCL keys here so that
248 // we can potentially read _future_ Terraform version config (to
249 // still be able to validate the required version).
251 // We should still keep track of unknown keys to validate later, but
252 // HCL doesn't currently support that.
255 if err := hcl.DecodeObject(&config, item.Val); err != nil {
256 return nil, fmt.Errorf(
257 "Error reading terraform config: %s",
261 // If we have provisioners, then parse those out
262 if os := listVal.Filter("backend"); len(os.Items) > 0 {
264 config.Backend, err = loadTerraformBackendHcl(os)
266 return nil, fmt.Errorf(
267 "Error reading backend config for terraform block: %s",
275 // Loads the Backend configuration from an object list.
276 func loadTerraformBackendHcl(list *ast.ObjectList) (*Backend, error) {
277 if len(list.Items) > 1 {
278 return nil, fmt.Errorf("only one 'backend' block allowed")
282 item := list.Items[0]
285 if len(item.Keys) != 1 {
286 return nil, fmt.Errorf(
287 "position %s: 'backend' must be followed by exactly one string: a type",
291 typ := item.Keys[0].Token.Value().(string)
293 // Decode the raw config
294 var config map[string]interface{}
295 if err := hcl.DecodeObject(&config, item.Val); err != nil {
296 return nil, fmt.Errorf(
297 "Error reading backend config: %s",
301 rawConfig, err := NewRawConfig(config)
303 return nil, fmt.Errorf(
304 "Error reading backend config: %s",
310 RawConfig: rawConfig,
317 // Given a handle to a HCL object, this transforms it into the Atlas
319 func loadAtlasHcl(list *ast.ObjectList) (*AtlasConfig, error) {
320 if len(list.Items) > 1 {
321 return nil, fmt.Errorf("only one 'atlas' block allowed")
325 item := list.Items[0]
327 var config AtlasConfig
328 if err := hcl.DecodeObject(&config, item.Val); err != nil {
329 return nil, fmt.Errorf(
330 "Error reading atlas config: %s",
337 // Given a handle to a HCL object, this recurses into the structure
338 // and pulls out a list of modules.
340 // The resulting modules may not be unique, but each module
341 // represents exactly one module definition in the HCL configuration.
342 // We leave it up to another pass to merge them together.
343 func loadModulesHcl(list *ast.ObjectList) ([]*Module, error) {
344 if err := assertAllBlocksHaveNames("module", list); err != nil {
348 list = list.Children()
349 if len(list.Items) == 0 {
353 // Where all the results will go
356 // Now go over all the types and their children in order to get
357 // all of the actual resources.
358 for _, item := range list.Items {
359 k := item.Keys[0].Token.Value().(string)
361 var listVal *ast.ObjectList
362 if ot, ok := item.Val.(*ast.ObjectType); ok {
365 return nil, fmt.Errorf("module '%s': should be an object", k)
368 var config map[string]interface{}
369 if err := hcl.DecodeObject(&config, item.Val); err != nil {
370 return nil, fmt.Errorf(
371 "Error reading config for %s: %s",
376 // Remove the fields we handle specially
377 delete(config, "source")
379 rawConfig, err := NewRawConfig(config)
381 return nil, fmt.Errorf(
382 "Error reading config for %s: %s",
387 // If we have a count, then figure it out
389 if o := listVal.Filter("source"); len(o.Items) > 0 {
390 err = hcl.DecodeObject(&source, o.Items[0].Val)
392 return nil, fmt.Errorf(
393 "Error parsing source for %s: %s",
399 result = append(result, &Module{
402 RawConfig: rawConfig,
409 // LoadOutputsHcl recurses into the given HCL object and turns
410 // it into a mapping of outputs.
411 func loadOutputsHcl(list *ast.ObjectList) ([]*Output, error) {
412 if err := assertAllBlocksHaveNames("output", list); err != nil {
416 list = list.Children()
418 // Go through each object and turn it into an actual result.
419 result := make([]*Output, 0, len(list.Items))
420 for _, item := range list.Items {
421 n := item.Keys[0].Token.Value().(string)
423 var listVal *ast.ObjectList
424 if ot, ok := item.Val.(*ast.ObjectType); ok {
427 return nil, fmt.Errorf("output '%s': should be an object", n)
430 var config map[string]interface{}
431 if err := hcl.DecodeObject(&config, item.Val); err != nil {
435 // Delete special keys
436 delete(config, "depends_on")
438 rawConfig, err := NewRawConfig(config)
440 return nil, fmt.Errorf(
441 "Error reading config for output %s: %s",
446 // If we have depends fields, then add those in
447 var dependsOn []string
448 if o := listVal.Filter("depends_on"); len(o.Items) > 0 {
449 err := hcl.DecodeObject(&dependsOn, o.Items[0].Val)
451 return nil, fmt.Errorf(
452 "Error reading depends_on for output %q: %s",
458 result = append(result, &Output{
460 RawConfig: rawConfig,
461 DependsOn: dependsOn,
468 // LoadVariablesHcl recurses into the given HCL object and turns
469 // it into a list of variables.
470 func loadVariablesHcl(list *ast.ObjectList) ([]*Variable, error) {
471 if err := assertAllBlocksHaveNames("variable", list); err != nil {
475 list = list.Children()
477 // hclVariable is the structure each variable is decoded into
478 type hclVariable struct {
479 DeclaredType string `hcl:"type"`
482 Fields []string `hcl:",decodedFields"`
485 // Go through each object and turn it into an actual result.
486 result := make([]*Variable, 0, len(list.Items))
487 for _, item := range list.Items {
488 // Clean up items from JSON
489 unwrapHCLObjectKeysFromJSON(item, 1)
492 if len(item.Keys) != 1 {
493 return nil, fmt.Errorf(
494 "position %s: 'variable' must be followed by exactly one strings: a name",
498 n := item.Keys[0].Token.Value().(string)
499 if !NameRegexp.MatchString(n) {
500 return nil, fmt.Errorf(
501 "position %s: 'variable' name must match regular expression: %s",
502 item.Pos(), NameRegexp)
505 // Check for invalid keys
506 valid := []string{"type", "default", "description"}
507 if err := checkHCLKeys(item.Val, valid); err != nil {
508 return nil, multierror.Prefix(err, fmt.Sprintf(
512 // Decode into hclVariable to get typed values
513 var hclVar hclVariable
514 if err := hcl.DecodeObject(&hclVar, item.Val); err != nil {
518 // Defaults turn into a slice of map[string]interface{} and
519 // we need to make sure to convert that down into the
520 // proper type for Config.
521 if ms, ok := hclVar.Default.([]map[string]interface{}); ok {
522 def := make(map[string]interface{})
523 for _, m := range ms {
524 for k, v := range m {
532 // Build the new variable and do some basic validation
535 DeclaredType: hclVar.DeclaredType,
536 Default: hclVar.Default,
537 Description: hclVar.Description,
539 if err := newVar.ValidateTypeAndDefault(); err != nil {
543 result = append(result, newVar)
549 // LoadProvidersHcl recurses into the given HCL object and turns
550 // it into a mapping of provider configs.
551 func loadProvidersHcl(list *ast.ObjectList) ([]*ProviderConfig, error) {
552 if err := assertAllBlocksHaveNames("provider", list); err != nil {
556 list = list.Children()
557 if len(list.Items) == 0 {
561 // Go through each object and turn it into an actual result.
562 result := make([]*ProviderConfig, 0, len(list.Items))
563 for _, item := range list.Items {
564 n := item.Keys[0].Token.Value().(string)
566 var listVal *ast.ObjectList
567 if ot, ok := item.Val.(*ast.ObjectType); ok {
570 return nil, fmt.Errorf("module '%s': should be an object", n)
573 var config map[string]interface{}
574 if err := hcl.DecodeObject(&config, item.Val); err != nil {
578 delete(config, "alias")
579 delete(config, "version")
581 rawConfig, err := NewRawConfig(config)
583 return nil, fmt.Errorf(
584 "Error reading config for provider config %s: %s",
589 // If we have an alias field, then add those in
591 if a := listVal.Filter("alias"); len(a.Items) > 0 {
592 err := hcl.DecodeObject(&alias, a.Items[0].Val)
594 return nil, fmt.Errorf(
595 "Error reading alias for provider[%s]: %s",
601 // If we have a version field then extract it
603 if a := listVal.Filter("version"); len(a.Items) > 0 {
604 err := hcl.DecodeObject(&version, a.Items[0].Val)
606 return nil, fmt.Errorf(
607 "Error reading version for provider[%s]: %s",
613 result = append(result, &ProviderConfig{
617 RawConfig: rawConfig,
624 // Given a handle to a HCL object, this recurses into the structure
625 // and pulls out a list of data sources.
627 // The resulting data sources may not be unique, but each one
628 // represents exactly one data definition in the HCL configuration.
629 // We leave it up to another pass to merge them together.
630 func loadDataResourcesHcl(list *ast.ObjectList) ([]*Resource, error) {
631 if err := assertAllBlocksHaveNames("data", list); err != nil {
635 list = list.Children()
636 if len(list.Items) == 0 {
640 // Where all the results will go
641 var result []*Resource
643 // Now go over all the types and their children in order to get
644 // all of the actual resources.
645 for _, item := range list.Items {
646 if len(item.Keys) != 2 {
647 return nil, fmt.Errorf(
648 "position %s: 'data' must be followed by exactly two strings: a type and a name",
652 t := item.Keys[0].Token.Value().(string)
653 k := item.Keys[1].Token.Value().(string)
655 var listVal *ast.ObjectList
656 if ot, ok := item.Val.(*ast.ObjectType); ok {
659 return nil, fmt.Errorf("data sources %s[%s]: should be an object", t, k)
662 var config map[string]interface{}
663 if err := hcl.DecodeObject(&config, item.Val); err != nil {
664 return nil, fmt.Errorf(
665 "Error reading config for %s[%s]: %s",
671 // Remove the fields we handle specially
672 delete(config, "depends_on")
673 delete(config, "provider")
674 delete(config, "count")
676 rawConfig, err := NewRawConfig(config)
678 return nil, fmt.Errorf(
679 "Error reading config for %s[%s]: %s",
685 // If we have a count, then figure it out
686 var count string = "1"
687 if o := listVal.Filter("count"); len(o.Items) > 0 {
688 err = hcl.DecodeObject(&count, o.Items[0].Val)
690 return nil, fmt.Errorf(
691 "Error parsing count for %s[%s]: %s",
697 countConfig, err := NewRawConfig(map[string]interface{}{
703 countConfig.Key = "count"
705 // If we have depends fields, then add those in
706 var dependsOn []string
707 if o := listVal.Filter("depends_on"); len(o.Items) > 0 {
708 err := hcl.DecodeObject(&dependsOn, o.Items[0].Val)
710 return nil, fmt.Errorf(
711 "Error reading depends_on for %s[%s]: %s",
718 // If we have a provider, then parse it out
720 if o := listVal.Filter("provider"); len(o.Items) > 0 {
721 err := hcl.DecodeObject(&provider, o.Items[0].Val)
723 return nil, fmt.Errorf(
724 "Error reading provider for %s[%s]: %s",
731 result = append(result, &Resource{
732 Mode: DataResourceMode,
735 RawCount: countConfig,
736 RawConfig: rawConfig,
738 Provisioners: []*Provisioner{},
739 DependsOn: dependsOn,
740 Lifecycle: ResourceLifecycle{},
747 // Given a handle to a HCL object, this recurses into the structure
748 // and pulls out a list of managed resources.
750 // The resulting resources may not be unique, but each resource
751 // represents exactly one "resource" block in the HCL configuration.
752 // We leave it up to another pass to merge them together.
753 func loadManagedResourcesHcl(list *ast.ObjectList) ([]*Resource, error) {
754 list = list.Children()
755 if len(list.Items) == 0 {
759 // Where all the results will go
760 var result []*Resource
762 // Now go over all the types and their children in order to get
763 // all of the actual resources.
764 for _, item := range list.Items {
765 // GH-4385: We detect a pure provisioner resource and give the user
766 // an error about how to do it cleanly.
767 if len(item.Keys) == 4 && item.Keys[2].Token.Value().(string) == "provisioner" {
768 return nil, fmt.Errorf(
769 "position %s: provisioners in a resource should be wrapped in a list\n\n"+
770 "Example: \"provisioner\": [ { \"local-exec\": ... } ]",
775 unwrapHCLObjectKeysFromJSON(item, 2)
777 if len(item.Keys) != 2 {
778 return nil, fmt.Errorf(
779 "position %s: resource must be followed by exactly two strings, a type and a name",
783 t := item.Keys[0].Token.Value().(string)
784 k := item.Keys[1].Token.Value().(string)
786 var listVal *ast.ObjectList
787 if ot, ok := item.Val.(*ast.ObjectType); ok {
790 return nil, fmt.Errorf("resources %s[%s]: should be an object", t, k)
793 var config map[string]interface{}
794 if err := hcl.DecodeObject(&config, item.Val); err != nil {
795 return nil, fmt.Errorf(
796 "Error reading config for %s[%s]: %s",
802 // Remove the fields we handle specially
803 delete(config, "connection")
804 delete(config, "count")
805 delete(config, "depends_on")
806 delete(config, "provisioner")
807 delete(config, "provider")
808 delete(config, "lifecycle")
810 rawConfig, err := NewRawConfig(config)
812 return nil, fmt.Errorf(
813 "Error reading config for %s[%s]: %s",
819 // If we have a count, then figure it out
820 var count string = "1"
821 if o := listVal.Filter("count"); len(o.Items) > 0 {
822 err = hcl.DecodeObject(&count, o.Items[0].Val)
824 return nil, fmt.Errorf(
825 "Error parsing count for %s[%s]: %s",
831 countConfig, err := NewRawConfig(map[string]interface{}{
837 countConfig.Key = "count"
839 // If we have depends fields, then add those in
840 var dependsOn []string
841 if o := listVal.Filter("depends_on"); len(o.Items) > 0 {
842 err := hcl.DecodeObject(&dependsOn, o.Items[0].Val)
844 return nil, fmt.Errorf(
845 "Error reading depends_on for %s[%s]: %s",
852 // If we have connection info, then parse those out
853 var connInfo map[string]interface{}
854 if o := listVal.Filter("connection"); len(o.Items) > 0 {
855 err := hcl.DecodeObject(&connInfo, o.Items[0].Val)
857 return nil, fmt.Errorf(
858 "Error reading connection info for %s[%s]: %s",
865 // If we have provisioners, then parse those out
866 var provisioners []*Provisioner
867 if os := listVal.Filter("provisioner"); len(os.Items) > 0 {
869 provisioners, err = loadProvisionersHcl(os, connInfo)
871 return nil, fmt.Errorf(
872 "Error reading provisioners for %s[%s]: %s",
879 // If we have a provider, then parse it out
881 if o := listVal.Filter("provider"); len(o.Items) > 0 {
882 err := hcl.DecodeObject(&provider, o.Items[0].Val)
884 return nil, fmt.Errorf(
885 "Error reading provider for %s[%s]: %s",
892 // Check if the resource should be re-created before
893 // destroying the existing instance
894 var lifecycle ResourceLifecycle
895 if o := listVal.Filter("lifecycle"); len(o.Items) > 0 {
896 if len(o.Items) > 1 {
897 return nil, fmt.Errorf(
898 "%s[%s]: Multiple lifecycle blocks found, expected one",
902 // Check for invalid keys
903 valid := []string{"create_before_destroy", "ignore_changes", "prevent_destroy"}
904 if err := checkHCLKeys(o.Items[0].Val, valid); err != nil {
905 return nil, multierror.Prefix(err, fmt.Sprintf(
909 var raw map[string]interface{}
910 if err = hcl.DecodeObject(&raw, o.Items[0].Val); err != nil {
911 return nil, fmt.Errorf(
912 "Error parsing lifecycle for %s[%s]: %s",
918 if err := mapstructure.WeakDecode(raw, &lifecycle); err != nil {
919 return nil, fmt.Errorf(
920 "Error parsing lifecycle for %s[%s]: %s",
927 result = append(result, &Resource{
928 Mode: ManagedResourceMode,
931 RawCount: countConfig,
932 RawConfig: rawConfig,
933 Provisioners: provisioners,
935 DependsOn: dependsOn,
936 Lifecycle: lifecycle,
943 func loadProvisionersHcl(list *ast.ObjectList, connInfo map[string]interface{}) ([]*Provisioner, error) {
944 if err := assertAllBlocksHaveNames("provisioner", list); err != nil {
948 list = list.Children()
949 if len(list.Items) == 0 {
953 // Go through each object and turn it into an actual result.
954 result := make([]*Provisioner, 0, len(list.Items))
955 for _, item := range list.Items {
956 n := item.Keys[0].Token.Value().(string)
958 var listVal *ast.ObjectList
959 if ot, ok := item.Val.(*ast.ObjectType); ok {
962 return nil, fmt.Errorf("provisioner '%s': should be an object", n)
965 var config map[string]interface{}
966 if err := hcl.DecodeObject(&config, item.Val); err != nil {
970 // Parse the "when" value
971 when := ProvisionerWhenCreate
972 if v, ok := config["when"]; ok {
975 when = ProvisionerWhenCreate
977 when = ProvisionerWhenDestroy
979 return nil, fmt.Errorf(
980 "position %s: 'provisioner' when must be 'create' or 'destroy'",
985 // Parse the "on_failure" value
986 onFailure := ProvisionerOnFailureFail
987 if v, ok := config["on_failure"]; ok {
990 onFailure = ProvisionerOnFailureContinue
992 onFailure = ProvisionerOnFailureFail
994 return nil, fmt.Errorf(
995 "position %s: 'provisioner' on_failure must be 'continue' or 'fail'",
1000 // Delete fields we special case
1001 delete(config, "connection")
1002 delete(config, "when")
1003 delete(config, "on_failure")
1005 rawConfig, err := NewRawConfig(config)
1010 // Check if we have a provisioner-level connection
1011 // block that overrides the resource-level
1012 var subConnInfo map[string]interface{}
1013 if o := listVal.Filter("connection"); len(o.Items) > 0 {
1014 err := hcl.DecodeObject(&subConnInfo, o.Items[0].Val)
1020 // Inherit from the resource connInfo any keys
1021 // that are not explicitly overriden.
1022 if connInfo != nil && subConnInfo != nil {
1023 for k, v := range connInfo {
1024 if _, ok := subConnInfo[k]; !ok {
1028 } else if subConnInfo == nil {
1029 subConnInfo = connInfo
1032 // Parse the connInfo
1033 connRaw, err := NewRawConfig(subConnInfo)
1038 result = append(result, &Provisioner{
1040 RawConfig: rawConfig,
1043 OnFailure: onFailure,
1051 func hclObjectMap(os *hclobj.Object) map[string]ast.ListNode {
1052 objects := make(map[string][]*hclobj.Object)
1054 for _, o := range os.Elem(false) {
1055 for _, elem := range o.Elem(true) {
1056 val, ok := objects[elem.Key]
1058 val = make([]*hclobj.Object, 0, 1)
1061 val = append(val, elem)
1062 objects[elem.Key] = val
1070 // assertAllBlocksHaveNames returns an error if any of the items in
1071 // the given object list are blocks without keys (like "module {}")
1072 // or simple assignments (like "module = 1"). It returns nil if
1073 // neither of these things are true.
1075 // The given name is used in any generated error messages, and should
1076 // be the name of the block we're dealing with. The given list should
1077 // be the result of calling .Filter on an object list with that same
1079 func assertAllBlocksHaveNames(name string, list *ast.ObjectList) error {
1080 if elem := list.Elem(); len(elem.Items) != 0 {
1081 switch et := elem.Items[0].Val.(type) {
1082 case *ast.ObjectType:
1084 return fmt.Errorf("%s: %q must be followed by a name", pos, name)
1086 pos := elem.Items[0].Val.Pos()
1087 return fmt.Errorf("%s: %q must be a configuration block", pos, name)
1093 func checkHCLKeys(node ast.Node, valid []string) error {
1094 var list *ast.ObjectList
1095 switch n := node.(type) {
1096 case *ast.ObjectList:
1098 case *ast.ObjectType:
1101 return fmt.Errorf("cannot check HCL keys of type %T", n)
1104 validMap := make(map[string]struct{}, len(valid))
1105 for _, v := range valid {
1106 validMap[v] = struct{}{}
1110 for _, item := range list.Items {
1111 key := item.Keys[0].Token.Value().(string)
1112 if _, ok := validMap[key]; !ok {
1113 result = multierror.Append(result, fmt.Errorf(
1114 "invalid key: %s", key))
1121 // unwrapHCLObjectKeysFromJSON cleans up an edge case that can occur when
1122 // parsing JSON as input: if we're parsing JSON then directly nested
1123 // items will show up as additional "keys".
1125 // For objects that expect a fixed number of keys, this breaks the
1126 // decoding process. This function unwraps the object into what it would've
1127 // looked like if it came directly from HCL by specifying the number of keys
1132 // { "foo": { "baz": {} } }
1134 // Will show up with Keys being: []string{"foo", "baz"}
1135 // when we really just want the first two. This function will fix this.
1136 func unwrapHCLObjectKeysFromJSON(item *ast.ObjectItem, depth int) {
1137 if len(item.Keys) > depth && item.Keys[0].Token.JSON {
1138 for len(item.Keys) > depth {
1139 // Pop off the last key
1141 key := item.Keys[n-1]
1142 item.Keys[n-1] = nil
1143 item.Keys = item.Keys[:n-1]
1145 // Wrap our value in a list
1146 item.Val = &ast.ObjectType{
1147 List: &ast.ObjectList{
1148 Items: []*ast.ObjectItem{
1150 Keys: []*ast.ObjectKey{key},