github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/terraform/variables.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/zclconf/go-cty/cty"
     7  
     8  	"github.com/hashicorp/terraform/internal/configs"
     9  	"github.com/hashicorp/terraform/internal/tfdiags"
    10  )
    11  
    12  // InputValue represents a raw value for a root module input variable as
    13  // provided by the external caller into a function like terraform.Context.Plan.
    14  //
    15  // InputValue should represent as directly as possible what the user set the
    16  // variable to, without any attempt to convert the value to the variable's
    17  // type constraint or substitute the configured default values for variables
    18  // that wasn't set. Those adjustments will be handled by Terraform Core itself
    19  // as part of performing the requested operation.
    20  //
    21  // A Terraform Core caller must provide an InputValue object for each of the
    22  // variables declared in the root module, even if the end user didn't provide
    23  // an explicit value for some of them. See the Value field documentation for
    24  // how to handle that situation.
    25  //
    26  // Terraform Core also internally uses InputValue to represent the raw value
    27  // provided for a variable in a child module call, following the same
    28  // conventions. However, that's an implementation detail not visible to
    29  // outside callers.
    30  type InputValue struct {
    31  	// Value is the raw value as provided by the user as part of the plan
    32  	// options, or a corresponding similar data structure for non-plan
    33  	// operations.
    34  	//
    35  	// If a particular variable declared in the root module is _not_ set by
    36  	// the user then the caller must still provide an InputValue for it but
    37  	// must set Value to cty.NilVal to represent the absense of a value.
    38  	// This requirement is to help detect situations where the caller isn't
    39  	// correctly detecting and handling all of the declared variables.
    40  	//
    41  	// For historical reasons it's important that callers distinguish the
    42  	// situation of the value not being set at all (cty.NilVal) from the
    43  	// situation of it being explicitly set to null (a cty.NullVal result):
    44  	// for "nullable" input variables that distinction unfortunately decides
    45  	// whether the final value will be the variable's default or will be
    46  	// explicitly null.
    47  	Value cty.Value
    48  
    49  	// SourceType is a high-level category for where the value of Value
    50  	// came from, which Terraform Core uses to tailor some of its error
    51  	// messages to be more helpful to the user.
    52  	//
    53  	// Some SourceType values should be accompanied by a populated SourceRange
    54  	// value. See that field's documentation below for more information.
    55  	SourceType ValueSourceType
    56  
    57  	// SourceRange provides source location information for values whose
    58  	// SourceType is either ValueFromConfig, ValueFromNamedFile, or
    59  	// ValueForNormalFile. It is not populated for other source types, and so
    60  	// should not be used.
    61  	SourceRange tfdiags.SourceRange
    62  }
    63  
    64  // ValueSourceType describes what broad category of source location provided
    65  // a particular value.
    66  type ValueSourceType rune
    67  
    68  const (
    69  	// ValueFromUnknown is the zero value of ValueSourceType and is not valid.
    70  	ValueFromUnknown ValueSourceType = 0
    71  
    72  	// ValueFromConfig indicates that a value came from a .tf or .tf.json file,
    73  	// e.g. the default value defined for a variable.
    74  	ValueFromConfig ValueSourceType = 'C'
    75  
    76  	// ValueFromAutoFile indicates that a value came from a "values file", like
    77  	// a .tfvars file, that was implicitly loaded by naming convention.
    78  	ValueFromAutoFile ValueSourceType = 'F'
    79  
    80  	// ValueFromNamedFile indicates that a value came from a named "values file",
    81  	// like a .tfvars file, that was passed explicitly on the command line (e.g.
    82  	// -var-file=foo.tfvars).
    83  	ValueFromNamedFile ValueSourceType = 'N'
    84  
    85  	// ValueFromCLIArg indicates that the value was provided directly in
    86  	// a CLI argument. The name of this argument is not recorded and so it must
    87  	// be inferred from context.
    88  	ValueFromCLIArg ValueSourceType = 'A'
    89  
    90  	// ValueFromEnvVar indicates that the value was provided via an environment
    91  	// variable. The name of the variable is not recorded and so it must be
    92  	// inferred from context.
    93  	ValueFromEnvVar ValueSourceType = 'E'
    94  
    95  	// ValueFromInput indicates that the value was provided at an interactive
    96  	// input prompt.
    97  	ValueFromInput ValueSourceType = 'I'
    98  
    99  	// ValueFromPlan indicates that the value was retrieved from a stored plan.
   100  	ValueFromPlan ValueSourceType = 'P'
   101  
   102  	// ValueFromCaller indicates that the value was explicitly overridden by
   103  	// a caller to Context.SetVariable after the context was constructed.
   104  	ValueFromCaller ValueSourceType = 'S'
   105  )
   106  
   107  func (v *InputValue) GoString() string {
   108  	if (v.SourceRange != tfdiags.SourceRange{}) {
   109  		return fmt.Sprintf("&terraform.InputValue{Value: %#v, SourceType: %#v, SourceRange: %#v}", v.Value, v.SourceType, v.SourceRange)
   110  	} else {
   111  		return fmt.Sprintf("&terraform.InputValue{Value: %#v, SourceType: %#v}", v.Value, v.SourceType)
   112  	}
   113  }
   114  
   115  // HasSourceRange returns true if the reciever has a source type for which
   116  // we expect the SourceRange field to be populated with a valid range.
   117  func (v *InputValue) HasSourceRange() bool {
   118  	return v.SourceType.HasSourceRange()
   119  }
   120  
   121  // HasSourceRange returns true if the reciever is one of the source types
   122  // that is used along with a valid SourceRange field when appearing inside an
   123  // InputValue object.
   124  func (v ValueSourceType) HasSourceRange() bool {
   125  	switch v {
   126  	case ValueFromConfig, ValueFromAutoFile, ValueFromNamedFile:
   127  		return true
   128  	default:
   129  		return false
   130  	}
   131  }
   132  
   133  func (v ValueSourceType) GoString() string {
   134  	return fmt.Sprintf("terraform.%s", v)
   135  }
   136  
   137  //go:generate go run golang.org/x/tools/cmd/stringer -type ValueSourceType
   138  
   139  // InputValues is a map of InputValue instances.
   140  type InputValues map[string]*InputValue
   141  
   142  // InputValuesFromCaller turns the given map of naked values into an
   143  // InputValues that attributes each value to "a caller", using the source
   144  // type ValueFromCaller. This is primarily useful for testing purposes.
   145  //
   146  // This should not be used as a general way to convert map[string]cty.Value
   147  // into InputValues, since in most real cases we want to set a suitable
   148  // other SourceType and possibly SourceRange value.
   149  func InputValuesFromCaller(vals map[string]cty.Value) InputValues {
   150  	ret := make(InputValues, len(vals))
   151  	for k, v := range vals {
   152  		ret[k] = &InputValue{
   153  			Value:      v,
   154  			SourceType: ValueFromCaller,
   155  		}
   156  	}
   157  	return ret
   158  }
   159  
   160  // Override merges the given value maps with the receiver, overriding any
   161  // conflicting keys so that the latest definition wins.
   162  func (vv InputValues) Override(others ...InputValues) InputValues {
   163  	// FIXME: This should check to see if any of the values are maps and
   164  	// merge them if so, in order to preserve the behavior from prior to
   165  	// Terraform 0.12.
   166  	ret := make(InputValues)
   167  	for k, v := range vv {
   168  		ret[k] = v
   169  	}
   170  	for _, other := range others {
   171  		for k, v := range other {
   172  			ret[k] = v
   173  		}
   174  	}
   175  	return ret
   176  }
   177  
   178  // JustValues returns a map that just includes the values, discarding the
   179  // source information.
   180  func (vv InputValues) JustValues() map[string]cty.Value {
   181  	ret := make(map[string]cty.Value, len(vv))
   182  	for k, v := range vv {
   183  		ret[k] = v.Value
   184  	}
   185  	return ret
   186  }
   187  
   188  // SameValues returns true if the given InputValues has the same values as
   189  // the receiever, disregarding the source types and source ranges.
   190  //
   191  // Values are compared using the cty "RawEquals" method, which means that
   192  // unknown values can be considered equal to one another if they are of the
   193  // same type.
   194  func (vv InputValues) SameValues(other InputValues) bool {
   195  	if len(vv) != len(other) {
   196  		return false
   197  	}
   198  
   199  	for k, v := range vv {
   200  		ov, exists := other[k]
   201  		if !exists {
   202  			return false
   203  		}
   204  		if !v.Value.RawEquals(ov.Value) {
   205  			return false
   206  		}
   207  	}
   208  
   209  	return true
   210  }
   211  
   212  // HasValues returns true if the reciever has the same values as in the given
   213  // map, disregarding the source types and source ranges.
   214  //
   215  // Values are compared using the cty "RawEquals" method, which means that
   216  // unknown values can be considered equal to one another if they are of the
   217  // same type.
   218  func (vv InputValues) HasValues(vals map[string]cty.Value) bool {
   219  	if len(vv) != len(vals) {
   220  		return false
   221  	}
   222  
   223  	for k, v := range vv {
   224  		oVal, exists := vals[k]
   225  		if !exists {
   226  			return false
   227  		}
   228  		if !v.Value.RawEquals(oVal) {
   229  			return false
   230  		}
   231  	}
   232  
   233  	return true
   234  }
   235  
   236  // Identical returns true if the given InputValues has the same values,
   237  // source types, and source ranges as the receiver.
   238  //
   239  // Values are compared using the cty "RawEquals" method, which means that
   240  // unknown values can be considered equal to one another if they are of the
   241  // same type.
   242  //
   243  // This method is primarily for testing. For most practical purposes, it's
   244  // better to use SameValues or HasValues.
   245  func (vv InputValues) Identical(other InputValues) bool {
   246  	if len(vv) != len(other) {
   247  		return false
   248  	}
   249  
   250  	for k, v := range vv {
   251  		ov, exists := other[k]
   252  		if !exists {
   253  			return false
   254  		}
   255  		if !v.Value.RawEquals(ov.Value) {
   256  			return false
   257  		}
   258  		if v.SourceType != ov.SourceType {
   259  			return false
   260  		}
   261  		if v.SourceRange != ov.SourceRange {
   262  			return false
   263  		}
   264  	}
   265  
   266  	return true
   267  }
   268  
   269  // checkInputVariables ensures that the caller provided an InputValue
   270  // definition for each root module variable declared in the configuration.
   271  // The caller must provide an InputVariables with keys exactly matching
   272  // the declared variables, though some of them may be marked explicitly
   273  // unset by their values being cty.NilVal.
   274  //
   275  // This doesn't perform any type checking, default value substitution, or
   276  // validation checks. Those are all handled during a graph walk when we
   277  // visit the graph nodes representing each root variable.
   278  //
   279  // The set of values is considered valid only if the returned diagnostics
   280  // does not contain errors. A valid set of values may still produce warnings,
   281  // which should be returned to the user.
   282  func checkInputVariables(vcs map[string]*configs.Variable, vs InputValues) tfdiags.Diagnostics {
   283  	var diags tfdiags.Diagnostics
   284  
   285  	for name := range vcs {
   286  		_, isSet := vs[name]
   287  		if !isSet {
   288  			// Always an error, since the caller should have produced an
   289  			// item with Value: cty.NilVal to be explicit that it offered
   290  			// an opportunity to set this variable.
   291  			diags = diags.Append(tfdiags.Sourceless(
   292  				tfdiags.Error,
   293  				"Unassigned variable",
   294  				fmt.Sprintf("The input variable %q has not been assigned a value. This is a bug in Terraform; please report it in a GitHub issue.", name),
   295  			))
   296  			continue
   297  		}
   298  	}
   299  
   300  	// Check for any variables that are assigned without being configured.
   301  	// This is always an implementation error in the caller, because we
   302  	// expect undefined variables to be caught during context construction
   303  	// where there is better context to report it well.
   304  	for name := range vs {
   305  		if _, defined := vcs[name]; !defined {
   306  			diags = diags.Append(tfdiags.Sourceless(
   307  				tfdiags.Error,
   308  				"Value assigned to undeclared variable",
   309  				fmt.Sprintf("A value was assigned to an undeclared input variable %q.", name),
   310  			))
   311  		}
   312  	}
   313  
   314  	return diags
   315  }