mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-28 01:41:48 -06:00
39e609d5fd
Previously we were using the experimental HCL 2 repository, but now we'll shift over to the v2 import path within the main HCL repository as part of actually releasing HCL 2.0 as stable. This is a mechanical search/replace to the new import paths. It also switches to the v2.0.0 release of HCL, which includes some new code that Terraform didn't previously have but should not change any behavior that matters for Terraform's purposes. For the moment the experimental HCL2 repository is still an indirect dependency via terraform-config-inspect, so it remains in our go.sum and vendor directories for the moment. Because terraform-config-inspect uses a much smaller subset of the HCL2 functionality, this does still manage to prune the vendor directory a little. A subsequent release of terraform-config-inspect should allow us to completely remove that old repository in a future commit.
314 lines
9.8 KiB
Go
314 lines
9.8 KiB
Go
package terraform
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
"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'
|
|
)
|
|
|
|
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.
|
|
//
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
for k, v := range vv {
|
|
ov, exists := other[k]
|
|
if !exists {
|
|
return false
|
|
}
|
|
if !v.Value.RawEquals(ov.Value) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// 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 true
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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),
|
|
))
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|