github.com/sfdevops1/terrra4orm@v0.11.12-beta1/config/raw_config.go (about)

     1  package config
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/gob"
     6  	"errors"
     7  	"strconv"
     8  	"sync"
     9  
    10  	"github.com/zclconf/go-cty/cty"
    11  	"github.com/zclconf/go-cty/cty/convert"
    12  
    13  	hcl2 "github.com/hashicorp/hcl2/hcl"
    14  	"github.com/hashicorp/hil"
    15  	"github.com/hashicorp/hil/ast"
    16  	"github.com/mitchellh/copystructure"
    17  	"github.com/mitchellh/reflectwalk"
    18  )
    19  
    20  // UnknownVariableValue is a sentinel value that can be used
    21  // to denote that the value of a variable is unknown at this time.
    22  // RawConfig uses this information to build up data about
    23  // unknown keys.
    24  const UnknownVariableValue = "74D93920-ED26-11E3-AC10-0800200C9A66"
    25  
    26  // RawConfig is a structure that holds a piece of configuration
    27  // where the overall structure is unknown since it will be used
    28  // to configure a plugin or some other similar external component.
    29  //
    30  // RawConfigs can be interpolated with variables that come from
    31  // other resources, user variables, etc.
    32  //
    33  // RawConfig supports a query-like interface to request
    34  // information from deep within the structure.
    35  type RawConfig struct {
    36  	Key string
    37  
    38  	// Only _one_ of Raw and Body may be populated at a time.
    39  	//
    40  	// In the normal case, Raw is populated and Body is nil.
    41  	//
    42  	// When the experimental HCL2 parsing mode is enabled, "Body"
    43  	// is populated and RawConfig serves only to transport the hcl2.Body
    44  	// through the rest of Terraform core so we can ultimately decode it
    45  	// once its schema is known.
    46  	//
    47  	// Once we transition to HCL2 as the primary representation, RawConfig
    48  	// should be removed altogether and the hcl2.Body should be passed
    49  	// around directly.
    50  
    51  	Raw  map[string]interface{}
    52  	Body hcl2.Body
    53  
    54  	Interpolations []ast.Node
    55  	Variables      map[string]InterpolatedVariable
    56  
    57  	lock        sync.Mutex
    58  	config      map[string]interface{}
    59  	unknownKeys []string
    60  }
    61  
    62  // NewRawConfig creates a new RawConfig structure and populates the
    63  // publicly readable struct fields.
    64  func NewRawConfig(raw map[string]interface{}) (*RawConfig, error) {
    65  	result := &RawConfig{Raw: raw}
    66  	if err := result.init(); err != nil {
    67  		return nil, err
    68  	}
    69  
    70  	return result, nil
    71  }
    72  
    73  // NewRawConfigHCL2 creates a new RawConfig that is serving as a capsule
    74  // to transport a hcl2.Body. In this mode, the publicly-readable struct
    75  // fields are not populated since all operations should instead be diverted
    76  // to the HCL2 body.
    77  //
    78  // For a RawConfig object constructed with this function, the only valid use
    79  // is to later retrieve the Body value and call its own methods. Callers
    80  // may choose to set and then later handle the Key field, in a manner
    81  // consistent with how it is handled by the Value method, but the Value
    82  // method itself must not be used.
    83  //
    84  // This is an experimental codepath to be used only by the HCL2 config loader.
    85  // Non-experimental parsing should _always_ use NewRawConfig to produce a
    86  // fully-functional RawConfig object.
    87  func NewRawConfigHCL2(body hcl2.Body) *RawConfig {
    88  	return &RawConfig{
    89  		Body: body,
    90  	}
    91  }
    92  
    93  // RawMap returns a copy of the RawConfig.Raw map.
    94  func (r *RawConfig) RawMap() map[string]interface{} {
    95  	r.lock.Lock()
    96  	defer r.lock.Unlock()
    97  
    98  	m := make(map[string]interface{})
    99  	for k, v := range r.Raw {
   100  		m[k] = v
   101  	}
   102  	return m
   103  }
   104  
   105  // Copy returns a copy of this RawConfig, uninterpolated.
   106  func (r *RawConfig) Copy() *RawConfig {
   107  	if r == nil {
   108  		return nil
   109  	}
   110  
   111  	r.lock.Lock()
   112  	defer r.lock.Unlock()
   113  
   114  	if r.Body != nil {
   115  		return NewRawConfigHCL2(r.Body)
   116  	}
   117  
   118  	newRaw := make(map[string]interface{})
   119  	for k, v := range r.Raw {
   120  		newRaw[k] = v
   121  	}
   122  
   123  	result, err := NewRawConfig(newRaw)
   124  	if err != nil {
   125  		panic("copy failed: " + err.Error())
   126  	}
   127  
   128  	result.Key = r.Key
   129  	return result
   130  }
   131  
   132  // Value returns the value of the configuration if this configuration
   133  // has a Key set. If this does not have a Key set, nil will be returned.
   134  func (r *RawConfig) Value() interface{} {
   135  	if c := r.Config(); c != nil {
   136  		if v, ok := c[r.Key]; ok {
   137  			return v
   138  		}
   139  	}
   140  
   141  	r.lock.Lock()
   142  	defer r.lock.Unlock()
   143  	return r.Raw[r.Key]
   144  }
   145  
   146  // Config returns the entire configuration with the variables
   147  // interpolated from any call to Interpolate.
   148  //
   149  // If any interpolated variables are unknown (value set to
   150  // UnknownVariableValue), the first non-container (map, slice, etc.) element
   151  // will be removed from the config. The keys of unknown variables
   152  // can be found using the UnknownKeys function.
   153  //
   154  // By pruning out unknown keys from the configuration, the raw
   155  // structure will always successfully decode into its ultimate
   156  // structure using something like mapstructure.
   157  func (r *RawConfig) Config() map[string]interface{} {
   158  	r.lock.Lock()
   159  	defer r.lock.Unlock()
   160  	return r.config
   161  }
   162  
   163  // Interpolate uses the given mapping of variable values and uses
   164  // those as the values to replace any variables in this raw
   165  // configuration.
   166  //
   167  // Any prior calls to Interpolate are replaced with this one.
   168  //
   169  // If a variable key is missing, this will panic.
   170  func (r *RawConfig) Interpolate(vs map[string]ast.Variable) error {
   171  	r.lock.Lock()
   172  	defer r.lock.Unlock()
   173  
   174  	config := langEvalConfig(vs)
   175  	return r.interpolate(func(root ast.Node) (interface{}, error) {
   176  		// None of the variables we need are computed, meaning we should
   177  		// be able to properly evaluate.
   178  		result, err := hil.Eval(root, config)
   179  		if err != nil {
   180  			return "", err
   181  		}
   182  
   183  		return result.Value, nil
   184  	})
   185  }
   186  
   187  // Merge merges another RawConfig into this one (overriding any conflicting
   188  // values in this config) and returns a new config. The original config
   189  // is not modified.
   190  func (r *RawConfig) Merge(other *RawConfig) *RawConfig {
   191  	r.lock.Lock()
   192  	defer r.lock.Unlock()
   193  
   194  	// Merge the raw configurations
   195  	raw := make(map[string]interface{})
   196  	for k, v := range r.Raw {
   197  		raw[k] = v
   198  	}
   199  	for k, v := range other.Raw {
   200  		raw[k] = v
   201  	}
   202  
   203  	// Create the result
   204  	result, err := NewRawConfig(raw)
   205  	if err != nil {
   206  		panic(err)
   207  	}
   208  
   209  	// Merge the interpolated results
   210  	result.config = make(map[string]interface{})
   211  	for k, v := range r.config {
   212  		result.config[k] = v
   213  	}
   214  	for k, v := range other.config {
   215  		result.config[k] = v
   216  	}
   217  
   218  	// Build the unknown keys
   219  	if len(r.unknownKeys) > 0 || len(other.unknownKeys) > 0 {
   220  		unknownKeys := make(map[string]struct{})
   221  		for _, k := range r.unknownKeys {
   222  			unknownKeys[k] = struct{}{}
   223  		}
   224  		for _, k := range other.unknownKeys {
   225  			unknownKeys[k] = struct{}{}
   226  		}
   227  
   228  		result.unknownKeys = make([]string, 0, len(unknownKeys))
   229  		for k, _ := range unknownKeys {
   230  			result.unknownKeys = append(result.unknownKeys, k)
   231  		}
   232  	}
   233  
   234  	return result
   235  }
   236  
   237  func (r *RawConfig) init() error {
   238  	r.lock.Lock()
   239  	defer r.lock.Unlock()
   240  
   241  	r.config = r.Raw
   242  	r.Interpolations = nil
   243  	r.Variables = nil
   244  
   245  	fn := func(node ast.Node) (interface{}, error) {
   246  		r.Interpolations = append(r.Interpolations, node)
   247  		vars, err := DetectVariables(node)
   248  		if err != nil {
   249  			return "", err
   250  		}
   251  
   252  		for _, v := range vars {
   253  			if r.Variables == nil {
   254  				r.Variables = make(map[string]InterpolatedVariable)
   255  			}
   256  
   257  			r.Variables[v.FullKey()] = v
   258  		}
   259  
   260  		return "", nil
   261  	}
   262  
   263  	walker := &interpolationWalker{F: fn}
   264  	if err := reflectwalk.Walk(r.Raw, walker); err != nil {
   265  		return err
   266  	}
   267  
   268  	return nil
   269  }
   270  
   271  func (r *RawConfig) interpolate(fn interpolationWalkerFunc) error {
   272  	if r.Body != nil {
   273  		// For RawConfigs created for the HCL2 experiement, callers must
   274  		// use the HCL2 Body API directly rather than interpolating via
   275  		// the RawConfig.
   276  		return errors.New("this feature is not yet supported under the HCL2 experiment")
   277  	}
   278  
   279  	config, err := copystructure.Copy(r.Raw)
   280  	if err != nil {
   281  		return err
   282  	}
   283  	r.config = config.(map[string]interface{})
   284  
   285  	w := &interpolationWalker{F: fn, Replace: true}
   286  	err = reflectwalk.Walk(r.config, w)
   287  	if err != nil {
   288  		return err
   289  	}
   290  
   291  	r.unknownKeys = w.unknownKeys
   292  	return nil
   293  }
   294  
   295  func (r *RawConfig) merge(r2 *RawConfig) *RawConfig {
   296  	if r == nil && r2 == nil {
   297  		return nil
   298  	}
   299  
   300  	if r == nil {
   301  		r = &RawConfig{}
   302  	}
   303  
   304  	rawRaw, err := copystructure.Copy(r.Raw)
   305  	if err != nil {
   306  		panic(err)
   307  	}
   308  
   309  	raw := rawRaw.(map[string]interface{})
   310  	if r2 != nil {
   311  		for k, v := range r2.Raw {
   312  			raw[k] = v
   313  		}
   314  	}
   315  
   316  	result, err := NewRawConfig(raw)
   317  	if err != nil {
   318  		panic(err)
   319  	}
   320  
   321  	return result
   322  }
   323  
   324  // couldBeInteger is a helper that determines if the represented value could
   325  // result in an integer.
   326  //
   327  // This function only works for RawConfigs that have "Key" set, meaning that
   328  // a single result can be produced. Calling this function will overwrite
   329  // the Config and Value results to be a test value.
   330  //
   331  // This function is conservative. If there is some doubt about whether the
   332  // result could be an integer -- for example, if it depends on a variable
   333  // whose type we don't know yet -- it will still return true.
   334  func (r *RawConfig) couldBeInteger() bool {
   335  	if r.Key == "" {
   336  		// un-keyed RawConfigs can never produce numbers
   337  		return false
   338  	}
   339  	if r.Body == nil {
   340  		// Normal path: using the interpolator in this package
   341  		// Interpolate with a fixed number to verify that its a number.
   342  		r.interpolate(func(root ast.Node) (interface{}, error) {
   343  			// Execute the node but transform the AST so that it returns
   344  			// a fixed value of "5" for all interpolations.
   345  			result, err := hil.Eval(
   346  				hil.FixedValueTransform(
   347  					root, &ast.LiteralNode{Value: "5", Typex: ast.TypeString}),
   348  				nil)
   349  			if err != nil {
   350  				return "", err
   351  			}
   352  
   353  			return result.Value, nil
   354  		})
   355  		_, err := strconv.ParseInt(r.Value().(string), 0, 0)
   356  		return err == nil
   357  	} else {
   358  		// HCL2 experiment path: using the HCL2 API via shims
   359  		//
   360  		// This path catches fewer situations because we have to assume all
   361  		// variables are entirely unknown in HCL2, rather than the assumption
   362  		// above that all variables can be numbers because names like "var.foo"
   363  		// are considered a single variable rather than an attribute access.
   364  		// This is fine in practice, because we get a definitive answer
   365  		// during the graph walk when we have real values to work with.
   366  		attrs, diags := r.Body.JustAttributes()
   367  		if diags.HasErrors() {
   368  			// This body is not just a single attribute with a value, so
   369  			// this can't be a number.
   370  			return false
   371  		}
   372  		attr, hasAttr := attrs[r.Key]
   373  		if !hasAttr {
   374  			return false
   375  		}
   376  		result, diags := hcl2EvalWithUnknownVars(attr.Expr)
   377  		if diags.HasErrors() {
   378  			// We'll conservatively assume that this error is a result of
   379  			// us not being ready to fully-populate the scope, and catch
   380  			// any further problems during the main graph walk.
   381  			return true
   382  		}
   383  
   384  		// If the result is convertable to number then we'll allow it.
   385  		// We do this because an unknown string is optimistically convertable
   386  		// to number (might be "5") but a _known_ string "hello" is not.
   387  		_, err := convert.Convert(result, cty.Number)
   388  		return err == nil
   389  	}
   390  }
   391  
   392  // UnknownKeys returns the keys of the configuration that are unknown
   393  // because they had interpolated variables that must be computed.
   394  func (r *RawConfig) UnknownKeys() []string {
   395  	r.lock.Lock()
   396  	defer r.lock.Unlock()
   397  	return r.unknownKeys
   398  }
   399  
   400  // See GobEncode
   401  func (r *RawConfig) GobDecode(b []byte) error {
   402  	var data gobRawConfig
   403  	err := gob.NewDecoder(bytes.NewReader(b)).Decode(&data)
   404  	if err != nil {
   405  		return err
   406  	}
   407  
   408  	r.Key = data.Key
   409  	r.Raw = data.Raw
   410  
   411  	return r.init()
   412  }
   413  
   414  // GobEncode is a custom Gob encoder to use so that we only include the
   415  // raw configuration. Interpolated variables and such are lost and the
   416  // tree of interpolated variables is recomputed on decode, since it is
   417  // referentially transparent.
   418  func (r *RawConfig) GobEncode() ([]byte, error) {
   419  	r.lock.Lock()
   420  	defer r.lock.Unlock()
   421  
   422  	data := gobRawConfig{
   423  		Key: r.Key,
   424  		Raw: r.Raw,
   425  	}
   426  
   427  	var buf bytes.Buffer
   428  	if err := gob.NewEncoder(&buf).Encode(data); err != nil {
   429  		return nil, err
   430  	}
   431  
   432  	return buf.Bytes(), nil
   433  }
   434  
   435  type gobRawConfig struct {
   436  	Key string
   437  	Raw map[string]interface{}
   438  }
   439  
   440  // langEvalConfig returns the evaluation configuration we use to execute.
   441  func langEvalConfig(vs map[string]ast.Variable) *hil.EvalConfig {
   442  	funcMap := make(map[string]ast.Function)
   443  	for k, v := range Funcs() {
   444  		funcMap[k] = v
   445  	}
   446  	funcMap["lookup"] = interpolationFuncLookup(vs)
   447  	funcMap["keys"] = interpolationFuncKeys(vs)
   448  	funcMap["values"] = interpolationFuncValues(vs)
   449  
   450  	return &hil.EvalConfig{
   451  		GlobalScope: &ast.BasicScope{
   452  			VarMap:  vs,
   453  			FuncMap: funcMap,
   454  		},
   455  	}
   456  }