github.com/gwilym/terraform@v0.3.8-0.20151231151641-c7573de75b19/config/loader_hcl.go (about)

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