github.com/tarrant/terraform@v0.3.8-0.20150402012457-f68c9eee638e/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  	objects := make(map[string]*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[o2.Key] = 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 n, 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  		rawConfig, err := NewRawConfig(config)
   352  		if err != nil {
   353  			return nil, fmt.Errorf(
   354  				"Error reading config for provider config %s: %s",
   355  				n,
   356  				err)
   357  		}
   358  
   359  		result = append(result, &ProviderConfig{
   360  			Name:      n,
   361  			RawConfig: rawConfig,
   362  		})
   363  	}
   364  
   365  	return result, nil
   366  }
   367  
   368  // Given a handle to a HCL object, this recurses into the structure
   369  // and pulls out a list of resources.
   370  //
   371  // The resulting resources may not be unique, but each resource
   372  // represents exactly one resource definition in the HCL configuration.
   373  // We leave it up to another pass to merge them together.
   374  func loadResourcesHcl(os *hclobj.Object) ([]*Resource, error) {
   375  	var allTypes []*hclobj.Object
   376  
   377  	// HCL object iteration is really nasty. Below is likely to make
   378  	// no sense to anyone approaching this code. Luckily, it is very heavily
   379  	// tested. If working on a bug fix or feature, we recommend writing a
   380  	// test first then doing whatever you want to the code below. If you
   381  	// break it, the tests will catch it. Likewise, if you change this,
   382  	// MAKE SURE you write a test for your change, because its fairly impossible
   383  	// to reason about this mess.
   384  	//
   385  	// Functionally, what the code does below is get the libucl.Objects
   386  	// for all the TYPES, such as "aws_security_group".
   387  	for _, o1 := range os.Elem(false) {
   388  		// Iterate the inner to get the list of types
   389  		for _, o2 := range o1.Elem(true) {
   390  			// Iterate all of this type to get _all_ the types
   391  			for _, o3 := range o2.Elem(false) {
   392  				allTypes = append(allTypes, o3)
   393  			}
   394  		}
   395  	}
   396  
   397  	// Where all the results will go
   398  	var result []*Resource
   399  
   400  	// Now go over all the types and their children in order to get
   401  	// all of the actual resources.
   402  	for _, t := range allTypes {
   403  		for _, obj := range t.Elem(true) {
   404  			k := obj.Key
   405  
   406  			var config map[string]interface{}
   407  			if err := hcl.DecodeObject(&config, obj); err != nil {
   408  				return nil, fmt.Errorf(
   409  					"Error reading config for %s[%s]: %s",
   410  					t.Key,
   411  					k,
   412  					err)
   413  			}
   414  
   415  			// Remove the fields we handle specially
   416  			delete(config, "connection")
   417  			delete(config, "count")
   418  			delete(config, "depends_on")
   419  			delete(config, "provisioner")
   420  			delete(config, "lifecycle")
   421  
   422  			rawConfig, err := NewRawConfig(config)
   423  			if 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  			// If we have a count, then figure it out
   432  			var count string = "1"
   433  			if o := obj.Get("count", false); o != nil {
   434  				err = hcl.DecodeObject(&count, o)
   435  				if err != nil {
   436  					return nil, fmt.Errorf(
   437  						"Error parsing count for %s[%s]: %s",
   438  						t.Key,
   439  						k,
   440  						err)
   441  				}
   442  			}
   443  			countConfig, err := NewRawConfig(map[string]interface{}{
   444  				"count": count,
   445  			})
   446  			if err != nil {
   447  				return nil, err
   448  			}
   449  			countConfig.Key = "count"
   450  
   451  			// If we have depends fields, then add those in
   452  			var dependsOn []string
   453  			if o := obj.Get("depends_on", false); o != nil {
   454  				err := hcl.DecodeObject(&dependsOn, o)
   455  				if err != nil {
   456  					return nil, fmt.Errorf(
   457  						"Error reading depends_on for %s[%s]: %s",
   458  						t.Key,
   459  						k,
   460  						err)
   461  				}
   462  			}
   463  
   464  			// If we have connection info, then parse those out
   465  			var connInfo map[string]interface{}
   466  			if o := obj.Get("connection", false); o != nil {
   467  				err := hcl.DecodeObject(&connInfo, o)
   468  				if err != nil {
   469  					return nil, fmt.Errorf(
   470  						"Error reading connection info for %s[%s]: %s",
   471  						t.Key,
   472  						k,
   473  						err)
   474  				}
   475  			}
   476  
   477  			// If we have provisioners, then parse those out
   478  			var provisioners []*Provisioner
   479  			if os := obj.Get("provisioner", false); os != nil {
   480  				var err error
   481  				provisioners, err = loadProvisionersHcl(os, connInfo)
   482  				if err != nil {
   483  					return nil, fmt.Errorf(
   484  						"Error reading provisioners for %s[%s]: %s",
   485  						t.Key,
   486  						k,
   487  						err)
   488  				}
   489  			}
   490  
   491  			// Check if the resource should be re-created before
   492  			// destroying the existing instance
   493  			var lifecycle ResourceLifecycle
   494  			if o := obj.Get("lifecycle", false); o != nil {
   495  				err = hcl.DecodeObject(&lifecycle, o)
   496  				if err != nil {
   497  					return nil, fmt.Errorf(
   498  						"Error parsing lifecycle for %s[%s]: %s",
   499  						t.Key,
   500  						k,
   501  						err)
   502  				}
   503  			}
   504  
   505  			result = append(result, &Resource{
   506  				Name:         k,
   507  				Type:         t.Key,
   508  				RawCount:     countConfig,
   509  				RawConfig:    rawConfig,
   510  				Provisioners: provisioners,
   511  				DependsOn:    dependsOn,
   512  				Lifecycle:    lifecycle,
   513  			})
   514  		}
   515  	}
   516  
   517  	return result, nil
   518  }
   519  
   520  func loadProvisionersHcl(os *hclobj.Object, connInfo map[string]interface{}) ([]*Provisioner, error) {
   521  	pos := make([]*hclobj.Object, 0, int(os.Len()))
   522  
   523  	// Accumulate all the actual provisioner configuration objects. We
   524  	// have to iterate twice here:
   525  	//
   526  	//  1. The first iteration is of the list of `provisioner` blocks.
   527  	//  2. The second iteration is of the dictionary within the
   528  	//      provisioner which will have only one element which is the
   529  	//      type of provisioner to use along with tis config.
   530  	//
   531  	// In JSON it looks kind of like this:
   532  	//
   533  	//   [
   534  	//     {
   535  	//       "shell": {
   536  	//         ...
   537  	//       }
   538  	//     }
   539  	//   ]
   540  	//
   541  	for _, o1 := range os.Elem(false) {
   542  		for _, o2 := range o1.Elem(true) {
   543  
   544  			switch o1.Type {
   545  			case hclobj.ValueTypeList:
   546  				for _, o3 := range o2.Elem(true) {
   547  					pos = append(pos, o3)
   548  				}
   549  			case hclobj.ValueTypeObject:
   550  				pos = append(pos, o2)
   551  			}
   552  		}
   553  	}
   554  
   555  	// Short-circuit if there are no items
   556  	if len(pos) == 0 {
   557  		return nil, nil
   558  	}
   559  
   560  	result := make([]*Provisioner, 0, len(pos))
   561  	for _, po := range pos {
   562  		var config map[string]interface{}
   563  		if err := hcl.DecodeObject(&config, po); err != nil {
   564  			return nil, err
   565  		}
   566  
   567  		// Delete the "connection" section, handle seperately
   568  		delete(config, "connection")
   569  
   570  		rawConfig, err := NewRawConfig(config)
   571  		if err != nil {
   572  			return nil, err
   573  		}
   574  
   575  		// Check if we have a provisioner-level connection
   576  		// block that overrides the resource-level
   577  		var subConnInfo map[string]interface{}
   578  		if o := po.Get("connection", false); o != nil {
   579  			err := hcl.DecodeObject(&subConnInfo, o)
   580  			if err != nil {
   581  				return nil, err
   582  			}
   583  		}
   584  
   585  		// Inherit from the resource connInfo any keys
   586  		// that are not explicitly overriden.
   587  		if connInfo != nil && subConnInfo != nil {
   588  			for k, v := range connInfo {
   589  				if _, ok := subConnInfo[k]; !ok {
   590  					subConnInfo[k] = v
   591  				}
   592  			}
   593  		} else if subConnInfo == nil {
   594  			subConnInfo = connInfo
   595  		}
   596  
   597  		// Parse the connInfo
   598  		connRaw, err := NewRawConfig(subConnInfo)
   599  		if err != nil {
   600  			return nil, err
   601  		}
   602  
   603  		result = append(result, &Provisioner{
   604  			Type:      po.Key,
   605  			RawConfig: rawConfig,
   606  			ConnInfo:  connRaw,
   607  		})
   608  	}
   609  
   610  	return result, nil
   611  }
   612  
   613  /*
   614  func hclObjectMap(os *hclobj.Object) map[string]ast.ListNode {
   615  	objects := make(map[string][]*hclobj.Object)
   616  
   617  	for _, o := range os.Elem(false) {
   618  		for _, elem := range o.Elem(true) {
   619  			val, ok := objects[elem.Key]
   620  			if !ok {
   621  				val = make([]*hclobj.Object, 0, 1)
   622  			}
   623  
   624  			val = append(val, elem)
   625  			objects[elem.Key] = val
   626  		}
   627  	}
   628  
   629  	return objects
   630  }
   631  */