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