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