mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-28 01:41:48 -06:00
e57a399d71
This changes the representation of maps in the interpolator from the dotted flatmap form of a string variable named "var.variablename.key" per map element to use native HIL maps instead. This involves porting some of the interpolation functions in order to keep the tests green, and adding support for map outputs. There is one backwards incompatibility: as a result of an implementation detail of maps, one could access an indexed map variable using the syntax "${var.variablename.key}". This is no longer possible - instead HIL native syntax - "${var.variablename["key"]}" must be used. This was previously documented, (though not heavily used) so it must be noted as a backward compatibility issue for Terraform 0.7.
153 lines
4.6 KiB
Go
153 lines
4.6 KiB
Go
package terraform
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/terraform/config"
|
|
"github.com/hashicorp/terraform/config/module"
|
|
"github.com/mitchellh/mapstructure"
|
|
)
|
|
|
|
// EvalTypeCheckVariable is an EvalNode which ensures that the variable
|
|
// values which are assigned as inputs to a module (including the root)
|
|
// match the types which are either declared for the variables explicitly
|
|
// or inferred from the default values.
|
|
//
|
|
// In order to achieve this three things are required:
|
|
// - a map of the proposed variable values
|
|
// - the configuration tree of the module in which the variable is
|
|
// declared
|
|
// - the path to the module (so we know which part of the tree to
|
|
// compare the values against).
|
|
//
|
|
// Currently since the type system is simple, we currently do not make
|
|
// use of the values since it is only valid to pass string values. The
|
|
// structure is in place for extension of the type system, however.
|
|
type EvalTypeCheckVariable struct {
|
|
Variables map[string]interface{}
|
|
ModulePath []string
|
|
ModuleTree *module.Tree
|
|
}
|
|
|
|
func (n *EvalTypeCheckVariable) Eval(ctx EvalContext) (interface{}, error) {
|
|
currentTree := n.ModuleTree
|
|
for _, pathComponent := range n.ModulePath[1:] {
|
|
currentTree = currentTree.Children()[pathComponent]
|
|
}
|
|
targetConfig := currentTree.Config()
|
|
|
|
prototypes := make(map[string]config.VariableType)
|
|
for _, variable := range targetConfig.Variables {
|
|
prototypes[variable.Name] = variable.Type()
|
|
}
|
|
|
|
// Only display a module in an error message if we are not in the root module
|
|
modulePathDescription := fmt.Sprintf(" in module %s", strings.Join(n.ModulePath[1:], "."))
|
|
if len(n.ModulePath) == 1 {
|
|
modulePathDescription = ""
|
|
}
|
|
|
|
for name, declaredType := range prototypes {
|
|
// This is only necessary when we _actually_ check. It is left as a reminder
|
|
// that at the current time we are dealing with a type system consisting only
|
|
// of strings and maps - where the only valid inter-module variable type is
|
|
// string.
|
|
proposedValue, ok := n.Variables[name]
|
|
if !ok {
|
|
// This means the default value should be used as no overriding value
|
|
// has been set. Therefore we should continue as no check is necessary.
|
|
continue
|
|
}
|
|
|
|
switch declaredType {
|
|
case config.VariableTypeString:
|
|
// This will need actual verification once we aren't dealing with
|
|
// a map[string]string but this is sufficient for now.
|
|
switch proposedValue.(type) {
|
|
case string:
|
|
continue
|
|
default:
|
|
return nil, fmt.Errorf("variable %s%s should be type %s, got %T",
|
|
name, modulePathDescription, declaredType.Printable(), proposedValue)
|
|
}
|
|
continue
|
|
case config.VariableTypeMap:
|
|
switch proposedValue.(type) {
|
|
case map[string]interface{}:
|
|
continue
|
|
default:
|
|
return nil, fmt.Errorf("variable %s%s should be type %s, got %T",
|
|
name, modulePathDescription, declaredType.Printable(), proposedValue)
|
|
}
|
|
default:
|
|
// This will need the actual type substituting when we have more than
|
|
// just strings and maps.
|
|
return nil, fmt.Errorf("variable %s%s should be type %s, got type string",
|
|
name, modulePathDescription, declaredType.Printable())
|
|
}
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
// EvalSetVariables is an EvalNode implementation that sets the variables
|
|
// explicitly for interpolation later.
|
|
type EvalSetVariables struct {
|
|
Module *string
|
|
Variables map[string]interface{}
|
|
}
|
|
|
|
// TODO: test
|
|
func (n *EvalSetVariables) Eval(ctx EvalContext) (interface{}, error) {
|
|
ctx.SetVariables(*n.Module, n.Variables)
|
|
return nil, nil
|
|
}
|
|
|
|
// EvalVariableBlock is an EvalNode implementation that evaluates the
|
|
// given configuration, and uses the final values as a way to set the
|
|
// mapping.
|
|
type EvalVariableBlock struct {
|
|
Config **ResourceConfig
|
|
VariableValues map[string]interface{}
|
|
}
|
|
|
|
// TODO: test
|
|
func (n *EvalVariableBlock) Eval(ctx EvalContext) (interface{}, error) {
|
|
// Clear out the existing mapping
|
|
for k, _ := range n.VariableValues {
|
|
delete(n.VariableValues, k)
|
|
}
|
|
|
|
// Get our configuration
|
|
rc := *n.Config
|
|
for k, v := range rc.Config {
|
|
var vString string
|
|
if err := mapstructure.WeakDecode(v, &vString); err == nil {
|
|
n.VariableValues[k] = vString
|
|
continue
|
|
}
|
|
|
|
var vMap map[string]interface{}
|
|
if err := mapstructure.WeakDecode(v, &vMap); err == nil {
|
|
n.VariableValues[k] = vMap
|
|
continue
|
|
}
|
|
|
|
var vSlice []interface{}
|
|
if err := mapstructure.WeakDecode(v, &vSlice); err == nil {
|
|
n.VariableValues[k] = vSlice
|
|
continue
|
|
}
|
|
|
|
return nil, fmt.Errorf("Variable value for %s is not a string, list or map type", k)
|
|
}
|
|
for k, _ := range rc.Raw {
|
|
if _, ok := n.VariableValues[k]; !ok {
|
|
n.VariableValues[k] = config.UnknownVariableValue
|
|
}
|
|
}
|
|
|
|
return nil, nil
|
|
}
|