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