github.com/jsoriano/terraform@v0.6.7-0.20151026070445-8b70867fdd95/config/loader_hcl.go (about)

     1  package config
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  
     7  	"github.com/hashicorp/hcl"
     8  	hclobj "github.com/hashicorp/hcl/hcl"
     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  	Object *hclobj.Object
    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  	if err := hcl.DecodeObject(&rawConfig, t.Object); err != nil {
    40  		return nil, err
    41  	}
    42  
    43  	// Start building up the actual configuration. We start with
    44  	// variables.
    45  	// TODO(mitchellh): Make function like loadVariablesHcl so that
    46  	// duplicates aren't overriden
    47  	config := new(Config)
    48  	if len(rawConfig.Variable) > 0 {
    49  		config.Variables = make([]*Variable, 0, len(rawConfig.Variable))
    50  		for k, v := range rawConfig.Variable {
    51  			// Defaults turn into a slice of map[string]interface{} and
    52  			// we need to make sure to convert that down into the
    53  			// proper type for Config.
    54  			if ms, ok := v.Default.([]map[string]interface{}); ok {
    55  				def := make(map[string]interface{})
    56  				for _, m := range ms {
    57  					for k, v := range m {
    58  						def[k] = v
    59  					}
    60  				}
    61  
    62  				v.Default = def
    63  			}
    64  
    65  			newVar := &Variable{
    66  				Name:        k,
    67  				Default:     v.Default,
    68  				Description: v.Description,
    69  			}
    70  
    71  			config.Variables = append(config.Variables, newVar)
    72  		}
    73  	}
    74  
    75  	// Get Atlas configuration
    76  	if atlas := t.Object.Get("atlas", false); atlas != nil {
    77  		var err error
    78  		config.Atlas, err = loadAtlasHcl(atlas)
    79  		if err != nil {
    80  			return nil, err
    81  		}
    82  	}
    83  
    84  	// Build the modules
    85  	if modules := t.Object.Get("module", false); modules != nil {
    86  		var err error
    87  		config.Modules, err = loadModulesHcl(modules)
    88  		if err != nil {
    89  			return nil, err
    90  		}
    91  	}
    92  
    93  	// Build the provider configs
    94  	if providers := t.Object.Get("provider", false); providers != nil {
    95  		var err error
    96  		config.ProviderConfigs, err = loadProvidersHcl(providers)
    97  		if err != nil {
    98  			return nil, err
    99  		}
   100  	}
   101  
   102  	// Build the resources
   103  	if resources := t.Object.Get("resource", false); resources != nil {
   104  		var err error
   105  		config.Resources, err = loadResourcesHcl(resources)
   106  		if err != nil {
   107  			return nil, err
   108  		}
   109  	}
   110  
   111  	// Build the outputs
   112  	if outputs := t.Object.Get("output", false); outputs != nil {
   113  		var err error
   114  		config.Outputs, err = loadOutputsHcl(outputs)
   115  		if err != nil {
   116  			return nil, err
   117  		}
   118  	}
   119  
   120  	// Check for invalid keys
   121  	for _, elem := range t.Object.Elem(true) {
   122  		k := elem.Key
   123  		if _, ok := validKeys[k]; ok {
   124  			continue
   125  		}
   126  
   127  		config.unknownKeys = append(config.unknownKeys, k)
   128  	}
   129  
   130  	return config, nil
   131  }
   132  
   133  // loadFileHcl is a fileLoaderFunc that knows how to read HCL
   134  // files and turn them into hclConfigurables.
   135  func loadFileHcl(root string) (configurable, []string, error) {
   136  	var obj *hclobj.Object = nil
   137  
   138  	// Read the HCL file and prepare for parsing
   139  	d, err := ioutil.ReadFile(root)
   140  	if err != nil {
   141  		return nil, nil, fmt.Errorf(
   142  			"Error reading %s: %s", root, err)
   143  	}
   144  
   145  	// Parse it
   146  	obj, err = hcl.Parse(string(d))
   147  	if err != nil {
   148  		return nil, nil, fmt.Errorf(
   149  			"Error parsing %s: %s", root, err)
   150  	}
   151  
   152  	// Start building the result
   153  	result := &hclConfigurable{
   154  		File:   root,
   155  		Object: obj,
   156  	}
   157  
   158  	// Dive in, find the imports. This is disabled for now since
   159  	// imports were removed prior to Terraform 0.1. The code is
   160  	// remaining here commented for historical purposes.
   161  	/*
   162  		imports := obj.Get("import")
   163  		if imports == nil {
   164  			result.Object.Ref()
   165  			return result, nil, nil
   166  		}
   167  
   168  		if imports.Type() != libucl.ObjectTypeString {
   169  			imports.Close()
   170  
   171  			return nil, nil, fmt.Errorf(
   172  				"Error in %s: all 'import' declarations should be in the format\n"+
   173  					"`import \"foo\"` (Got type %s)",
   174  				root,
   175  				imports.Type())
   176  		}
   177  
   178  		// Gather all the import paths
   179  		importPaths := make([]string, 0, imports.Len())
   180  		iter := imports.Iterate(false)
   181  		for imp := iter.Next(); imp != nil; imp = iter.Next() {
   182  			path := imp.ToString()
   183  			if !filepath.IsAbs(path) {
   184  				// Relative paths are relative to the Terraform file itself
   185  				dir := filepath.Dir(root)
   186  				path = filepath.Join(dir, path)
   187  			}
   188  
   189  			importPaths = append(importPaths, path)
   190  			imp.Close()
   191  		}
   192  		iter.Close()
   193  		imports.Close()
   194  
   195  		result.Object.Ref()
   196  	*/
   197  
   198  	return result, nil, nil
   199  }
   200  
   201  // Given a handle to a HCL object, this transforms it into the Atlas
   202  // configuration.
   203  func loadAtlasHcl(obj *hclobj.Object) (*AtlasConfig, error) {
   204  	var config AtlasConfig
   205  	if err := hcl.DecodeObject(&config, obj); err != nil {
   206  		return nil, fmt.Errorf(
   207  			"Error reading atlas config: %s",
   208  			err)
   209  	}
   210  
   211  	return &config, nil
   212  }
   213  
   214  // Given a handle to a HCL object, this recurses into the structure
   215  // and pulls out a list of modules.
   216  //
   217  // The resulting modules may not be unique, but each module
   218  // represents exactly one module definition in the HCL configuration.
   219  // We leave it up to another pass to merge them together.
   220  func loadModulesHcl(os *hclobj.Object) ([]*Module, error) {
   221  	var allNames []*hclobj.Object
   222  
   223  	// See loadResourcesHcl for why this exists. Don't touch this.
   224  	for _, o1 := range os.Elem(false) {
   225  		// Iterate the inner to get the list of types
   226  		for _, o2 := range o1.Elem(true) {
   227  			// Iterate all of this type to get _all_ the types
   228  			for _, o3 := range o2.Elem(false) {
   229  				allNames = append(allNames, o3)
   230  			}
   231  		}
   232  	}
   233  
   234  	// Where all the results will go
   235  	var result []*Module
   236  
   237  	// Now go over all the types and their children in order to get
   238  	// all of the actual resources.
   239  	for _, obj := range allNames {
   240  		k := obj.Key
   241  
   242  		var config map[string]interface{}
   243  		if err := hcl.DecodeObject(&config, obj); err != nil {
   244  			return nil, fmt.Errorf(
   245  				"Error reading config for %s: %s",
   246  				k,
   247  				err)
   248  		}
   249  
   250  		// Remove the fields we handle specially
   251  		delete(config, "source")
   252  
   253  		rawConfig, err := NewRawConfig(config)
   254  		if err != nil {
   255  			return nil, fmt.Errorf(
   256  				"Error reading config for %s: %s",
   257  				k,
   258  				err)
   259  		}
   260  
   261  		// If we have a count, then figure it out
   262  		var source string
   263  		if o := obj.Get("source", false); o != nil {
   264  			err = hcl.DecodeObject(&source, o)
   265  			if err != nil {
   266  				return nil, fmt.Errorf(
   267  					"Error parsing source for %s: %s",
   268  					k,
   269  					err)
   270  			}
   271  		}
   272  
   273  		result = append(result, &Module{
   274  			Name:      k,
   275  			Source:    source,
   276  			RawConfig: rawConfig,
   277  		})
   278  	}
   279  
   280  	return result, nil
   281  }
   282  
   283  // LoadOutputsHcl recurses into the given HCL object and turns
   284  // it into a mapping of outputs.
   285  func loadOutputsHcl(os *hclobj.Object) ([]*Output, error) {
   286  	objects := make(map[string]*hclobj.Object)
   287  
   288  	// Iterate over all the "output" blocks and get the keys along with
   289  	// their raw configuration objects. We'll parse those later.
   290  	for _, o1 := range os.Elem(false) {
   291  		for _, o2 := range o1.Elem(true) {
   292  			objects[o2.Key] = o2
   293  		}
   294  	}
   295  
   296  	if len(objects) == 0 {
   297  		return nil, nil
   298  	}
   299  
   300  	// Go through each object and turn it into an actual result.
   301  	result := make([]*Output, 0, len(objects))
   302  	for n, o := range objects {
   303  		var config map[string]interface{}
   304  
   305  		if err := hcl.DecodeObject(&config, o); err != nil {
   306  			return nil, err
   307  		}
   308  
   309  		rawConfig, err := NewRawConfig(config)
   310  		if err != nil {
   311  			return nil, fmt.Errorf(
   312  				"Error reading config for output %s: %s",
   313  				n,
   314  				err)
   315  		}
   316  
   317  		result = append(result, &Output{
   318  			Name:      n,
   319  			RawConfig: rawConfig,
   320  		})
   321  	}
   322  
   323  	return result, nil
   324  }
   325  
   326  // LoadProvidersHcl recurses into the given HCL object and turns
   327  // it into a mapping of provider configs.
   328  func loadProvidersHcl(os *hclobj.Object) ([]*ProviderConfig, error) {
   329  	var objects []*hclobj.Object
   330  
   331  	// Iterate over all the "provider" blocks and get the keys along with
   332  	// their raw configuration objects. We'll parse those later.
   333  	for _, o1 := range os.Elem(false) {
   334  		for _, o2 := range o1.Elem(true) {
   335  			objects = append(objects, o2)
   336  		}
   337  	}
   338  
   339  	if len(objects) == 0 {
   340  		return nil, nil
   341  	}
   342  
   343  	// Go through each object and turn it into an actual result.
   344  	result := make([]*ProviderConfig, 0, len(objects))
   345  	for _, o := range objects {
   346  		var config map[string]interface{}
   347  
   348  		if err := hcl.DecodeObject(&config, o); err != nil {
   349  			return nil, err
   350  		}
   351  
   352  		delete(config, "alias")
   353  
   354  		rawConfig, err := NewRawConfig(config)
   355  		if err != nil {
   356  			return nil, fmt.Errorf(
   357  				"Error reading config for provider config %s: %s",
   358  				o.Key,
   359  				err)
   360  		}
   361  
   362  		// If we have an alias field, then add those in
   363  		var alias string
   364  		if a := o.Get("alias", false); a != nil {
   365  			err := hcl.DecodeObject(&alias, a)
   366  			if err != nil {
   367  				return nil, fmt.Errorf(
   368  					"Error reading alias for provider[%s]: %s",
   369  					o.Key,
   370  					err)
   371  			}
   372  		}
   373  
   374  		result = append(result, &ProviderConfig{
   375  			Name:      o.Key,
   376  			Alias:     alias,
   377  			RawConfig: rawConfig,
   378  		})
   379  	}
   380  
   381  	return result, nil
   382  }
   383  
   384  // Given a handle to a HCL object, this recurses into the structure
   385  // and pulls out a list of resources.
   386  //
   387  // The resulting resources may not be unique, but each resource
   388  // represents exactly one resource definition in the HCL configuration.
   389  // We leave it up to another pass to merge them together.
   390  func loadResourcesHcl(os *hclobj.Object) ([]*Resource, error) {
   391  	var allTypes []*hclobj.Object
   392  
   393  	// HCL object iteration is really nasty. Below is likely to make
   394  	// no sense to anyone approaching this code. Luckily, it is very heavily
   395  	// tested. If working on a bug fix or feature, we recommend writing a
   396  	// test first then doing whatever you want to the code below. If you
   397  	// break it, the tests will catch it. Likewise, if you change this,
   398  	// MAKE SURE you write a test for your change, because its fairly impossible
   399  	// to reason about this mess.
   400  	//
   401  	// Functionally, what the code does below is get the libucl.Objects
   402  	// for all the TYPES, such as "aws_security_group".
   403  	for _, o1 := range os.Elem(false) {
   404  		// Iterate the inner to get the list of types
   405  		for _, o2 := range o1.Elem(true) {
   406  			// Iterate all of this type to get _all_ the types
   407  			for _, o3 := range o2.Elem(false) {
   408  				allTypes = append(allTypes, o3)
   409  			}
   410  		}
   411  	}
   412  
   413  	// Where all the results will go
   414  	var result []*Resource
   415  
   416  	// Now go over all the types and their children in order to get
   417  	// all of the actual resources.
   418  	for _, t := range allTypes {
   419  		for _, obj := range t.Elem(true) {
   420  			k := obj.Key
   421  
   422  			var config map[string]interface{}
   423  			if err := hcl.DecodeObject(&config, obj); err != nil {
   424  				return nil, fmt.Errorf(
   425  					"Error reading config for %s[%s]: %s",
   426  					t.Key,
   427  					k,
   428  					err)
   429  			}
   430  
   431  			// Remove the fields we handle specially
   432  			delete(config, "connection")
   433  			delete(config, "count")
   434  			delete(config, "depends_on")
   435  			delete(config, "provisioner")
   436  			delete(config, "provider")
   437  			delete(config, "lifecycle")
   438  
   439  			rawConfig, err := NewRawConfig(config)
   440  			if err != nil {
   441  				return nil, fmt.Errorf(
   442  					"Error reading config for %s[%s]: %s",
   443  					t.Key,
   444  					k,
   445  					err)
   446  			}
   447  
   448  			// If we have a count, then figure it out
   449  			var count string = "1"
   450  			if o := obj.Get("count", false); o != nil {
   451  				err = hcl.DecodeObject(&count, o)
   452  				if err != nil {
   453  					return nil, fmt.Errorf(
   454  						"Error parsing count for %s[%s]: %s",
   455  						t.Key,
   456  						k,
   457  						err)
   458  				}
   459  			}
   460  			countConfig, err := NewRawConfig(map[string]interface{}{
   461  				"count": count,
   462  			})
   463  			if err != nil {
   464  				return nil, err
   465  			}
   466  			countConfig.Key = "count"
   467  
   468  			// If we have depends fields, then add those in
   469  			var dependsOn []string
   470  			if o := obj.Get("depends_on", false); o != nil {
   471  				err := hcl.DecodeObject(&dependsOn, o)
   472  				if err != nil {
   473  					return nil, fmt.Errorf(
   474  						"Error reading depends_on for %s[%s]: %s",
   475  						t.Key,
   476  						k,
   477  						err)
   478  				}
   479  			}
   480  
   481  			// If we have connection info, then parse those out
   482  			var connInfo map[string]interface{}
   483  			if o := obj.Get("connection", false); o != nil {
   484  				err := hcl.DecodeObject(&connInfo, o)
   485  				if err != nil {
   486  					return nil, fmt.Errorf(
   487  						"Error reading connection info for %s[%s]: %s",
   488  						t.Key,
   489  						k,
   490  						err)
   491  				}
   492  			}
   493  
   494  			// If we have provisioners, then parse those out
   495  			var provisioners []*Provisioner
   496  			if os := obj.Get("provisioner", false); os != nil {
   497  				var err error
   498  				provisioners, err = loadProvisionersHcl(os, connInfo)
   499  				if err != nil {
   500  					return nil, fmt.Errorf(
   501  						"Error reading provisioners for %s[%s]: %s",
   502  						t.Key,
   503  						k,
   504  						err)
   505  				}
   506  			}
   507  
   508  			// If we have a provider, then parse it out
   509  			var provider string
   510  			if o := obj.Get("provider", false); o != nil {
   511  				err := hcl.DecodeObject(&provider, o)
   512  				if err != nil {
   513  					return nil, fmt.Errorf(
   514  						"Error reading provider for %s[%s]: %s",
   515  						t.Key,
   516  						k,
   517  						err)
   518  				}
   519  			}
   520  
   521  			// Check if the resource should be re-created before
   522  			// destroying the existing instance
   523  			var lifecycle ResourceLifecycle
   524  			if o := obj.Get("lifecycle", false); o != nil {
   525  				var raw map[string]interface{}
   526  				if err = hcl.DecodeObject(&raw, o); err != nil {
   527  					return nil, fmt.Errorf(
   528  						"Error parsing lifecycle for %s[%s]: %s",
   529  						t.Key,
   530  						k,
   531  						err)
   532  				}
   533  
   534  				if err := mapstructure.WeakDecode(raw, &lifecycle); err != nil {
   535  					return nil, fmt.Errorf(
   536  						"Error parsing lifecycle for %s[%s]: %s",
   537  						t.Key,
   538  						k,
   539  						err)
   540  				}
   541  			}
   542  
   543  			result = append(result, &Resource{
   544  				Name:         k,
   545  				Type:         t.Key,
   546  				RawCount:     countConfig,
   547  				RawConfig:    rawConfig,
   548  				Provisioners: provisioners,
   549  				Provider:     provider,
   550  				DependsOn:    dependsOn,
   551  				Lifecycle:    lifecycle,
   552  			})
   553  		}
   554  	}
   555  
   556  	return result, nil
   557  }
   558  
   559  func loadProvisionersHcl(os *hclobj.Object, connInfo map[string]interface{}) ([]*Provisioner, error) {
   560  	pos := make([]*hclobj.Object, 0, int(os.Len()))
   561  
   562  	// Accumulate all the actual provisioner configuration objects. We
   563  	// have to iterate twice here:
   564  	//
   565  	//  1. The first iteration is of the list of `provisioner` blocks.
   566  	//  2. The second iteration is of the dictionary within the
   567  	//      provisioner which will have only one element which is the
   568  	//      type of provisioner to use along with tis config.
   569  	//
   570  	// In JSON it looks kind of like this:
   571  	//
   572  	//   [
   573  	//     {
   574  	//       "shell": {
   575  	//         ...
   576  	//       }
   577  	//     }
   578  	//   ]
   579  	//
   580  	for _, o1 := range os.Elem(false) {
   581  		for _, o2 := range o1.Elem(true) {
   582  
   583  			switch o1.Type {
   584  			case hclobj.ValueTypeList:
   585  				for _, o3 := range o2.Elem(true) {
   586  					pos = append(pos, o3)
   587  				}
   588  			case hclobj.ValueTypeObject:
   589  				pos = append(pos, o2)
   590  			}
   591  		}
   592  	}
   593  
   594  	// Short-circuit if there are no items
   595  	if len(pos) == 0 {
   596  		return nil, nil
   597  	}
   598  
   599  	result := make([]*Provisioner, 0, len(pos))
   600  	for _, po := range pos {
   601  		var config map[string]interface{}
   602  		if err := hcl.DecodeObject(&config, po); 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 := po.Get("connection", false); o != nil {
   618  			err := hcl.DecodeObject(&subConnInfo, o)
   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:      po.Key,
   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  */