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