]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blobdiff - vendor/github.com/hashicorp/terraform/terraform/variables.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / terraform / variables.go
index 300f2adb1ac51d9004525b9820bf64654e26e6ad..75531b2bf33b6b1bec66565fafad9e4d605ec8c5 100644 (file)
@@ -2,165 +2,312 @@ package terraform
 
 import (
        "fmt"
-       "os"
-       "strings"
 
-       "github.com/hashicorp/terraform/config"
-       "github.com/hashicorp/terraform/config/module"
-       "github.com/hashicorp/terraform/helper/hilmapstructure"
+       "github.com/hashicorp/hcl2/hcl"
+       "github.com/zclconf/go-cty/cty"
+       "github.com/zclconf/go-cty/cty/convert"
+
+       "github.com/hashicorp/terraform/configs"
+       "github.com/hashicorp/terraform/tfdiags"
+)
+
+// InputValue represents a value for a variable in the root module, provided
+// as part of the definition of an operation.
+type InputValue struct {
+       Value      cty.Value
+       SourceType ValueSourceType
+
+       // SourceRange provides source location information for values whose
+       // SourceType is either ValueFromConfig or ValueFromFile. It is not
+       // populated for other source types, and so should not be used.
+       SourceRange tfdiags.SourceRange
+}
+
+// ValueSourceType describes what broad category of source location provided
+// a particular value.
+type ValueSourceType rune
+
+const (
+       // ValueFromUnknown is the zero value of ValueSourceType and is not valid.
+       ValueFromUnknown ValueSourceType = 0
+
+       // ValueFromConfig indicates that a value came from a .tf or .tf.json file,
+       // e.g. the default value defined for a variable.
+       ValueFromConfig ValueSourceType = 'C'
+
+       // ValueFromAutoFile indicates that a value came from a "values file", like
+       // a .tfvars file, that was implicitly loaded by naming convention.
+       ValueFromAutoFile ValueSourceType = 'F'
+
+       // ValueFromNamedFile indicates that a value came from a named "values file",
+       // like a .tfvars file, that was passed explicitly on the command line (e.g.
+       // -var-file=foo.tfvars).
+       ValueFromNamedFile ValueSourceType = 'N'
+
+       // ValueFromCLIArg indicates that the value was provided directly in
+       // a CLI argument. The name of this argument is not recorded and so it must
+       // be inferred from context.
+       ValueFromCLIArg ValueSourceType = 'A'
+
+       // ValueFromEnvVar indicates that the value was provided via an environment
+       // variable. The name of the variable is not recorded and so it must be
+       // inferred from context.
+       ValueFromEnvVar ValueSourceType = 'E'
+
+       // ValueFromInput indicates that the value was provided at an interactive
+       // input prompt.
+       ValueFromInput ValueSourceType = 'I'
+
+       // ValueFromPlan indicates that the value was retrieved from a stored plan.
+       ValueFromPlan ValueSourceType = 'P'
+
+       // ValueFromCaller indicates that the value was explicitly overridden by
+       // a caller to Context.SetVariable after the context was constructed.
+       ValueFromCaller ValueSourceType = 'S'
 )
 
-// Variables returns the fully loaded set of variables to use with
-// ContextOpts and NewContext, loading any additional variables from
-// the environment or any other sources.
+func (v *InputValue) GoString() string {
+       if (v.SourceRange != tfdiags.SourceRange{}) {
+               return fmt.Sprintf("&terraform.InputValue{Value: %#v, SourceType: %#v, SourceRange: %#v}", v.Value, v.SourceType, v.SourceRange)
+       } else {
+               return fmt.Sprintf("&terraform.InputValue{Value: %#v, SourceType: %#v}", v.Value, v.SourceType)
+       }
+}
+
+func (v ValueSourceType) GoString() string {
+       return fmt.Sprintf("terraform.%s", v)
+}
+
+//go:generate stringer -type ValueSourceType
+
+// InputValues is a map of InputValue instances.
+type InputValues map[string]*InputValue
+
+// InputValuesFromCaller turns the given map of naked values into an
+// InputValues that attributes each value to "a caller", using the source
+// type ValueFromCaller. This is primarily useful for testing purposes.
 //
-// The given module tree doesn't need to be loaded.
-func Variables(
-       m *module.Tree,
-       override map[string]interface{}) (map[string]interface{}, error) {
-       result := make(map[string]interface{})
-
-       // Variables are loaded in the following sequence. Each additional step
-       // will override conflicting variable keys from prior steps:
-       //
-       //   * Take default values from config
-       //   * Take values from TF_VAR_x env vars
-       //   * Take values specified in the "override" param which is usually
-       //     from -var, -var-file, etc.
-       //
-
-       // First load from the config
-       for _, v := range m.Config().Variables {
-               // If the var has no default, ignore
-               if v.Default == nil {
-                       continue
+// This should not be used as a general way to convert map[string]cty.Value
+// into InputValues, since in most real cases we want to set a suitable
+// other SourceType and possibly SourceRange value.
+func InputValuesFromCaller(vals map[string]cty.Value) InputValues {
+       ret := make(InputValues, len(vals))
+       for k, v := range vals {
+               ret[k] = &InputValue{
+                       Value:      v,
+                       SourceType: ValueFromCaller,
                }
+       }
+       return ret
+}
 
-               // If the type isn't a string, we use it as-is since it is a rich type
-               if v.Type() != config.VariableTypeString {
-                       result[v.Name] = v.Default
-                       continue
+// Override merges the given value maps with the receiver, overriding any
+// conflicting keys so that the latest definition wins.
+func (vv InputValues) Override(others ...InputValues) InputValues {
+       // FIXME: This should check to see if any of the values are maps and
+       // merge them if so, in order to preserve the behavior from prior to
+       // Terraform 0.12.
+       ret := make(InputValues)
+       for k, v := range vv {
+               ret[k] = v
+       }
+       for _, other := range others {
+               for k, v := range other {
+                       ret[k] = v
                }
+       }
+       return ret
+}
 
-               // v.Default has already been parsed as HCL but it may be an int type
-               switch typedDefault := v.Default.(type) {
-               case string:
-                       if typedDefault == "" {
-                               continue
-                       }
-                       result[v.Name] = typedDefault
-               case int, int64:
-                       result[v.Name] = fmt.Sprintf("%d", typedDefault)
-               case float32, float64:
-                       result[v.Name] = fmt.Sprintf("%f", typedDefault)
-               case bool:
-                       result[v.Name] = fmt.Sprintf("%t", typedDefault)
-               default:
-                       panic(fmt.Sprintf(
-                               "Unknown default var type: %T\n\n"+
-                                       "THIS IS A BUG. Please report it.",
-                               v.Default))
-               }
+// JustValues returns a map that just includes the values, discarding the
+// source information.
+func (vv InputValues) JustValues() map[string]cty.Value {
+       ret := make(map[string]cty.Value, len(vv))
+       for k, v := range vv {
+               ret[k] = v.Value
        }
+       return ret
+}
 
-       // Load from env vars
-       for _, v := range os.Environ() {
-               if !strings.HasPrefix(v, VarEnvPrefix) {
+// DefaultVariableValues returns an InputValues map representing the default
+// values specified for variables in the given configuration map.
+func DefaultVariableValues(configs map[string]*configs.Variable) InputValues {
+       ret := make(InputValues)
+       for k, c := range configs {
+               if c.Default == cty.NilVal {
                        continue
                }
+               ret[k] = &InputValue{
+                       Value:       c.Default,
+                       SourceType:  ValueFromConfig,
+                       SourceRange: tfdiags.SourceRangeFromHCL(c.DeclRange),
+               }
+       }
+       return ret
+}
 
-               // Strip off the prefix and get the value after the first "="
-               idx := strings.Index(v, "=")
-               k := v[len(VarEnvPrefix):idx]
-               v = v[idx+1:]
-
-               // Override the configuration-default values. Note that *not* finding the variable
-               // in configuration is OK, as we don't want to preclude people from having multiple
-               // sets of TF_VAR_whatever in their environment even if it is a little weird.
-               for _, schema := range m.Config().Variables {
-                       if schema.Name != k {
-                               continue
-                       }
-
-                       varType := schema.Type()
-                       varVal, err := parseVariableAsHCL(k, v, varType)
-                       if err != nil {
-                               return nil, err
-                       }
+// SameValues returns true if the given InputValues has the same values as
+// the receiever, disregarding the source types and source ranges.
+//
+// Values are compared using the cty "RawEquals" method, which means that
+// unknown values can be considered equal to one another if they are of the
+// same type.
+func (vv InputValues) SameValues(other InputValues) bool {
+       if len(vv) != len(other) {
+               return false
+       }
 
-                       switch varType {
-                       case config.VariableTypeMap:
-                               if err := varSetMap(result, k, varVal); err != nil {
-                                       return nil, err
-                               }
-                       default:
-                               result[k] = varVal
-                       }
+       for k, v := range vv {
+               ov, exists := other[k]
+               if !exists {
+                       return false
+               }
+               if !v.Value.RawEquals(ov.Value) {
+                       return false
                }
        }
 
-       // Load from overrides
-       for k, v := range override {
-               for _, schema := range m.Config().Variables {
-                       if schema.Name != k {
-                               continue
-                       }
+       return true
+}
 
-                       switch schema.Type() {
-                       case config.VariableTypeList:
-                               result[k] = v
-                       case config.VariableTypeMap:
-                               if err := varSetMap(result, k, v); err != nil {
-                                       return nil, err
-                               }
-                       case config.VariableTypeString:
-                               // Convert to a string and set. We don't catch any errors
-                               // here because the validation step later should catch
-                               // any type errors.
-                               var strVal string
-                               if err := hilmapstructure.WeakDecode(v, &strVal); err == nil {
-                                       result[k] = strVal
-                               } else {
-                                       result[k] = v
-                               }
-                       default:
-                               panic(fmt.Sprintf(
-                                       "Unhandled var type: %T\n\n"+
-                                               "THIS IS A BUG. Please report it.",
-                                       schema.Type()))
-                       }
+// HasValues returns true if the reciever has the same values as in the given
+// map, disregarding the source types and source ranges.
+//
+// Values are compared using the cty "RawEquals" method, which means that
+// unknown values can be considered equal to one another if they are of the
+// same type.
+func (vv InputValues) HasValues(vals map[string]cty.Value) bool {
+       if len(vv) != len(vals) {
+               return false
+       }
+
+       for k, v := range vv {
+               oVal, exists := vals[k]
+               if !exists {
+                       return false
+               }
+               if !v.Value.RawEquals(oVal) {
+                       return false
                }
        }
 
-       return result, nil
+       return true
 }
 
-// varSetMap sets or merges the map in "v" with the key "k" in the
-// "current" set of variables. This is just a private function to remove
-// duplicate logic in Variables
-func varSetMap(current map[string]interface{}, k string, v interface{}) error {
-       existing, ok := current[k]
-       if !ok {
-               current[k] = v
-               return nil
+// Identical returns true if the given InputValues has the same values,
+// source types, and source ranges as the receiver.
+//
+// Values are compared using the cty "RawEquals" method, which means that
+// unknown values can be considered equal to one another if they are of the
+// same type.
+//
+// This method is primarily for testing. For most practical purposes, it's
+// better to use SameValues or HasValues.
+func (vv InputValues) Identical(other InputValues) bool {
+       if len(vv) != len(other) {
+               return false
        }
 
-       existingMap, ok := existing.(map[string]interface{})
-       if !ok {
-               panic(fmt.Sprintf("%q is not a map, this is a bug in Terraform.", k))
+       for k, v := range vv {
+               ov, exists := other[k]
+               if !exists {
+                       return false
+               }
+               if !v.Value.RawEquals(ov.Value) {
+                       return false
+               }
+               if v.SourceType != ov.SourceType {
+                       return false
+               }
+               if v.SourceRange != ov.SourceRange {
+                       return false
+               }
        }
 
-       switch typedV := v.(type) {
-       case []map[string]interface{}:
-               for newKey, newVal := range typedV[0] {
-                       existingMap[newKey] = newVal
+       return true
+}
+
+// checkInputVariables ensures that variable values supplied at the UI conform
+// to their corresponding declarations in configuration.
+//
+// The set of values is considered valid only if the returned diagnostics
+// does not contain errors. A valid set of values may still produce warnings,
+// which should be returned to the user.
+func checkInputVariables(vcs map[string]*configs.Variable, vs InputValues) tfdiags.Diagnostics {
+       var diags tfdiags.Diagnostics
+
+       for name, vc := range vcs {
+               val, isSet := vs[name]
+               if !isSet {
+                       // Always an error, since the caller should already have included
+                       // default values from the configuration in the values map.
+                       diags = diags.Append(tfdiags.Sourceless(
+                               tfdiags.Error,
+                               "Unassigned variable",
+                               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),
+                       ))
+                       continue
                }
-       case map[string]interface{}:
-               for newKey, newVal := range typedV {
-                       existingMap[newKey] = newVal
+
+               wantType := vc.Type
+
+               // A given value is valid if it can convert to the desired type.
+               _, err := convert.Convert(val.Value, wantType)
+               if err != nil {
+                       switch val.SourceType {
+                       case ValueFromConfig, ValueFromAutoFile, ValueFromNamedFile:
+                               // We have source location information for these.
+                               diags = diags.Append(&hcl.Diagnostic{
+                                       Severity: hcl.DiagError,
+                                       Summary:  "Invalid value for input variable",
+                                       Detail:   fmt.Sprintf("The given value is not valid for variable %q: %s.", name, err),
+                                       Subject:  val.SourceRange.ToHCL().Ptr(),
+                               })
+                       case ValueFromEnvVar:
+                               diags = diags.Append(tfdiags.Sourceless(
+                                       tfdiags.Error,
+                                       "Invalid value for input variable",
+                                       fmt.Sprintf("The environment variable TF_VAR_%s does not contain a valid value for variable %q: %s.", name, name, err),
+                               ))
+                       case ValueFromCLIArg:
+                               diags = diags.Append(tfdiags.Sourceless(
+                                       tfdiags.Error,
+                                       "Invalid value for input variable",
+                                       fmt.Sprintf("The argument -var=\"%s=...\" does not contain a valid value for variable %q: %s.", name, name, err),
+                               ))
+                       case ValueFromInput:
+                               diags = diags.Append(tfdiags.Sourceless(
+                                       tfdiags.Error,
+                                       "Invalid value for input variable",
+                                       fmt.Sprintf("The value entered for variable %q is not valid: %s.", name, err),
+                               ))
+                       default:
+                               // The above gets us good coverage for the situations users
+                               // are likely to encounter with their own inputs. The other
+                               // cases are generally implementation bugs, so we'll just
+                               // use a generic error for these.
+                               diags = diags.Append(tfdiags.Sourceless(
+                                       tfdiags.Error,
+                                       "Invalid value for input variable",
+                                       fmt.Sprintf("The value provided for variable %q is not valid: %s.", name, err),
+                               ))
+                       }
                }
-       default:
-               return fmt.Errorf("variable %q should be type map, got %s", k, hclTypeName(v))
        }
-       return nil
+
+       // Check for any variables that are assigned without being configured.
+       // This is always an implementation error in the caller, because we
+       // expect undefined variables to be caught during context construction
+       // where there is better context to report it well.
+       for name := range vs {
+               if _, defined := vcs[name]; !defined {
+                       diags = diags.Append(tfdiags.Sourceless(
+                               tfdiags.Error,
+                               "Value assigned to undeclared variable",
+                               fmt.Sprintf("A value was assigned to an undeclared input variable %q.", name),
+                       ))
+               }
+       }
+
+       return diags
 }