core: Use native HIL maps instead of flatmaps
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.
This commit is contained in:
parent
6aac79e194
commit
e57a399d71
|
@ -394,29 +394,31 @@ func outputsAsString(state *terraform.State, schema []*config.Output) string {
|
|||
|
||||
// Output the outputs in alphabetical order
|
||||
keyLen := 0
|
||||
keys := make([]string, 0, len(outputs))
|
||||
ks := make([]string, 0, len(outputs))
|
||||
for key, _ := range outputs {
|
||||
keys = append(keys, key)
|
||||
ks = append(ks, key)
|
||||
if len(key) > keyLen {
|
||||
keyLen = len(key)
|
||||
}
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, k := range keys {
|
||||
v := outputs[k]
|
||||
sort.Strings(ks)
|
||||
|
||||
for _, k := range ks {
|
||||
if schemaMap[k].Sensitive {
|
||||
outputBuf.WriteString(fmt.Sprintf(
|
||||
" %s%s = <sensitive>\n",
|
||||
k,
|
||||
strings.Repeat(" ", keyLen-len(k))))
|
||||
} else {
|
||||
outputBuf.WriteString(fmt.Sprintf(
|
||||
" %s%s = %s\n",
|
||||
k,
|
||||
strings.Repeat(" ", keyLen-len(k)),
|
||||
v))
|
||||
outputBuf.WriteString(fmt.Sprintf("%s = <sensitive>\n", k))
|
||||
continue
|
||||
}
|
||||
|
||||
v := outputs[k]
|
||||
switch typedV := v.(type) {
|
||||
case string:
|
||||
outputBuf.WriteString(fmt.Sprintf("%s = %s\n", k, typedV))
|
||||
case []interface{}:
|
||||
outputBuf.WriteString(formatListOutput("", k, typedV))
|
||||
outputBuf.WriteString("\n")
|
||||
case map[string]interface{}:
|
||||
outputBuf.WriteString(formatMapOutput("", k, typedV))
|
||||
outputBuf.WriteString("\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
@ -27,7 +29,7 @@ func (c *OutputCommand) Run(args []string) int {
|
|||
}
|
||||
|
||||
args = cmdFlags.Args()
|
||||
if len(args) > 1 {
|
||||
if len(args) > 2 {
|
||||
c.Ui.Error(
|
||||
"The output command expects exactly one argument with the name\n" +
|
||||
"of an output variable or no arguments to show all outputs.\n")
|
||||
|
@ -40,6 +42,11 @@ func (c *OutputCommand) Run(args []string) int {
|
|||
name = args[0]
|
||||
}
|
||||
|
||||
index := ""
|
||||
if len(args) > 1 {
|
||||
index = args[1]
|
||||
}
|
||||
|
||||
stateStore, err := c.Meta.State()
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error reading state: %s", err))
|
||||
|
@ -74,17 +81,7 @@ func (c *OutputCommand) Run(args []string) int {
|
|||
}
|
||||
|
||||
if name == "" {
|
||||
ks := make([]string, 0, len(mod.Outputs))
|
||||
for k, _ := range mod.Outputs {
|
||||
ks = append(ks, k)
|
||||
}
|
||||
sort.Strings(ks)
|
||||
|
||||
for _, k := range ks {
|
||||
v := mod.Outputs[k]
|
||||
|
||||
c.Ui.Output(fmt.Sprintf("%s = %s", k, v))
|
||||
}
|
||||
c.Ui.Output(outputsAsString(state))
|
||||
return 0
|
||||
}
|
||||
|
||||
|
@ -101,6 +98,44 @@ func (c *OutputCommand) Run(args []string) int {
|
|||
switch output := v.(type) {
|
||||
case string:
|
||||
c.Ui.Output(output)
|
||||
return 0
|
||||
case []interface{}:
|
||||
if index == "" {
|
||||
c.Ui.Output(formatListOutput("", "", output))
|
||||
break
|
||||
}
|
||||
|
||||
indexInt, err := strconv.Atoi(index)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
"The index %q requested is not valid for the list output\n"+
|
||||
"%q - indices must be numeric, and in the range 0-%d", index, name,
|
||||
len(output)-1))
|
||||
break
|
||||
}
|
||||
|
||||
if indexInt < 0 || indexInt >= len(output) {
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
"The index %d requested is not valid for the list output\n"+
|
||||
"%q - indices must be in the range 0-%d", indexInt, name,
|
||||
len(output)-1))
|
||||
break
|
||||
}
|
||||
|
||||
c.Ui.Output(fmt.Sprintf("%s", output[indexInt]))
|
||||
return 0
|
||||
case map[string]interface{}:
|
||||
if index == "" {
|
||||
c.Ui.Output(formatMapOutput("", "", output))
|
||||
break
|
||||
}
|
||||
|
||||
if value, ok := output[index]; ok {
|
||||
c.Ui.Output(fmt.Sprintf("%s", value))
|
||||
return 0
|
||||
} else {
|
||||
return 1
|
||||
}
|
||||
default:
|
||||
panic(fmt.Errorf("Unknown output type: %T", output))
|
||||
}
|
||||
|
@ -108,6 +143,53 @@ func (c *OutputCommand) Run(args []string) int {
|
|||
return 0
|
||||
}
|
||||
|
||||
func formatListOutput(indent, outputName string, outputList []interface{}) string {
|
||||
keyIndent := ""
|
||||
|
||||
outputBuf := new(bytes.Buffer)
|
||||
if outputName != "" {
|
||||
outputBuf.WriteString(fmt.Sprintf("%s%s = [", indent, outputName))
|
||||
keyIndent = " "
|
||||
}
|
||||
|
||||
for _, value := range outputList {
|
||||
outputBuf.WriteString(fmt.Sprintf("\n%s%s%s", indent, keyIndent, value))
|
||||
}
|
||||
|
||||
if outputName != "" {
|
||||
outputBuf.WriteString(fmt.Sprintf("\n%s]", indent))
|
||||
}
|
||||
|
||||
return strings.TrimPrefix(outputBuf.String(), "\n")
|
||||
}
|
||||
|
||||
func formatMapOutput(indent, outputName string, outputMap map[string]interface{}) string {
|
||||
ks := make([]string, 0, len(outputMap))
|
||||
for k, _ := range outputMap {
|
||||
ks = append(ks, k)
|
||||
}
|
||||
sort.Strings(ks)
|
||||
|
||||
keyIndent := ""
|
||||
|
||||
outputBuf := new(bytes.Buffer)
|
||||
if outputName != "" {
|
||||
outputBuf.WriteString(fmt.Sprintf("%s%s = {", indent, outputName))
|
||||
keyIndent = " "
|
||||
}
|
||||
|
||||
for _, k := range ks {
|
||||
v := outputMap[k]
|
||||
outputBuf.WriteString(fmt.Sprintf("\n%s%s%s = %v", indent, keyIndent, k, v))
|
||||
}
|
||||
|
||||
if outputName != "" {
|
||||
outputBuf.WriteString(fmt.Sprintf("\n%s}", indent))
|
||||
}
|
||||
|
||||
return strings.TrimPrefix(outputBuf.String(), "\n")
|
||||
}
|
||||
|
||||
func (c *OutputCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: terraform output [options] [NAME]
|
||||
|
|
|
@ -11,7 +11,6 @@ import (
|
|||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/hil"
|
||||
"github.com/hashicorp/hil/ast"
|
||||
"github.com/hashicorp/terraform/flatmap"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/mitchellh/reflectwalk"
|
||||
)
|
||||
|
@ -239,7 +238,7 @@ func (c *Config) Validate() error {
|
|||
}
|
||||
|
||||
interp := false
|
||||
fn := func(ast.Node) (string, error) {
|
||||
fn := func(ast.Node) (interface{}, error) {
|
||||
interp = true
|
||||
return "", nil
|
||||
}
|
||||
|
@ -450,7 +449,7 @@ func (c *Config) Validate() error {
|
|||
}
|
||||
|
||||
// Interpolate with a fixed number to verify that its a number.
|
||||
r.RawCount.interpolate(func(root ast.Node) (string, error) {
|
||||
r.RawCount.interpolate(func(root ast.Node) (interface{}, error) {
|
||||
// Execute the node but transform the AST so that it returns
|
||||
// a fixed value of "5" for all interpolations.
|
||||
result, err := hil.Eval(
|
||||
|
@ -461,7 +460,7 @@ func (c *Config) Validate() error {
|
|||
return "", err
|
||||
}
|
||||
|
||||
return result.Value.(string), nil
|
||||
return result.Value, nil
|
||||
})
|
||||
_, err := strconv.ParseInt(r.RawCount.Value().(string), 0, 0)
|
||||
if err != nil {
|
||||
|
@ -809,28 +808,6 @@ func (r *Resource) mergerMerge(m merger) merger {
|
|||
return &result
|
||||
}
|
||||
|
||||
// DefaultsMap returns a map of default values for this variable.
|
||||
func (v *Variable) DefaultsMap() map[string]string {
|
||||
if v.Default == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
n := fmt.Sprintf("var.%s", v.Name)
|
||||
switch v.Type() {
|
||||
case VariableTypeString:
|
||||
return map[string]string{n: v.Default.(string)}
|
||||
case VariableTypeMap:
|
||||
result := flatmap.Flatten(map[string]interface{}{
|
||||
n: v.Default.(map[string]string),
|
||||
})
|
||||
result[n] = v.Name
|
||||
|
||||
return result
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Merge merges two variables to create a new third variable.
|
||||
func (v *Variable) Merge(v2 *Variable) *Variable {
|
||||
// Shallow copy the variable
|
||||
|
|
|
@ -2,7 +2,6 @@ package config
|
|||
|
||||
import (
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
@ -458,43 +457,6 @@ func TestProviderConfigName(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestVariableDefaultsMap(t *testing.T) {
|
||||
cases := []struct {
|
||||
Default interface{}
|
||||
Output map[string]string
|
||||
}{
|
||||
{
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
|
||||
{
|
||||
"foo",
|
||||
map[string]string{"var.foo": "foo"},
|
||||
},
|
||||
|
||||
{
|
||||
map[interface{}]interface{}{
|
||||
"foo": "bar",
|
||||
"bar": "baz",
|
||||
},
|
||||
map[string]string{
|
||||
"var.foo": "foo",
|
||||
"var.foo.foo": "bar",
|
||||
"var.foo.bar": "baz",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
v := &Variable{Name: "foo", Default: tc.Default}
|
||||
actual := v.DefaultsMap()
|
||||
if !reflect.DeepEqual(actual, tc.Output) {
|
||||
t.Fatalf("%d: bad: %#v", i, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testConfig(t *testing.T, name string) *Config {
|
||||
c, err := LoadFile(filepath.Join(fixtureDir, name, "main.tf"))
|
||||
if err != nil {
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
|
||||
"github.com/apparentlymart/go-cidr/cidr"
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/hil"
|
||||
"github.com/hashicorp/hil/ast"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
@ -466,20 +467,22 @@ func interpolationFuncSplit() ast.Function {
|
|||
// dynamic lookups of map types within a Terraform configuration.
|
||||
func interpolationFuncLookup(vs map[string]ast.Variable) ast.Function {
|
||||
return ast.Function{
|
||||
ArgTypes: []ast.Type{ast.TypeString, ast.TypeString},
|
||||
ArgTypes: []ast.Type{ast.TypeMap, ast.TypeString},
|
||||
ReturnType: ast.TypeString,
|
||||
Callback: func(args []interface{}) (interface{}, error) {
|
||||
k := fmt.Sprintf("var.%s.%s", args[0].(string), args[1].(string))
|
||||
v, ok := vs[k]
|
||||
index := args[1].(string)
|
||||
mapVar := args[0].(map[string]ast.Variable)
|
||||
|
||||
v, ok := mapVar[index]
|
||||
if !ok {
|
||||
return "", fmt.Errorf(
|
||||
"lookup in '%s' failed to find '%s'",
|
||||
args[0].(string), args[1].(string))
|
||||
"lookup failed to find '%s'",
|
||||
args[1].(string))
|
||||
}
|
||||
if v.Type != ast.TypeString {
|
||||
return "", fmt.Errorf(
|
||||
"lookup in '%s' for '%s' has bad type %s",
|
||||
args[0].(string), args[1].(string), v.Type)
|
||||
"lookup for '%s' has bad type %s",
|
||||
args[1].(string), v.Type)
|
||||
}
|
||||
|
||||
return v.Value.(string), nil
|
||||
|
@ -513,28 +516,24 @@ func interpolationFuncElement() ast.Function {
|
|||
// keys of map types within a Terraform configuration.
|
||||
func interpolationFuncKeys(vs map[string]ast.Variable) ast.Function {
|
||||
return ast.Function{
|
||||
ArgTypes: []ast.Type{ast.TypeString},
|
||||
ReturnType: ast.TypeString,
|
||||
ArgTypes: []ast.Type{ast.TypeMap},
|
||||
ReturnType: ast.TypeList,
|
||||
Callback: func(args []interface{}) (interface{}, error) {
|
||||
// Prefix must include ending dot to be a map
|
||||
prefix := fmt.Sprintf("var.%s.", args[0].(string))
|
||||
keys := make([]string, 0, len(vs))
|
||||
for k, _ := range vs {
|
||||
if !strings.HasPrefix(k, prefix) {
|
||||
continue
|
||||
}
|
||||
keys = append(keys, k[len(prefix):])
|
||||
}
|
||||
mapVar := args[0].(map[string]ast.Variable)
|
||||
keys := make([]string, 0)
|
||||
|
||||
if len(keys) <= 0 {
|
||||
return "", fmt.Errorf(
|
||||
"failed to find map '%s'",
|
||||
args[0].(string))
|
||||
for k, _ := range mapVar {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
return NewStringList(keys).String(), nil
|
||||
variable, err := hil.InterfaceToVariable(keys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return variable.Value, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -543,38 +542,34 @@ func interpolationFuncKeys(vs map[string]ast.Variable) ast.Function {
|
|||
// keys of map types within a Terraform configuration.
|
||||
func interpolationFuncValues(vs map[string]ast.Variable) ast.Function {
|
||||
return ast.Function{
|
||||
ArgTypes: []ast.Type{ast.TypeString},
|
||||
ReturnType: ast.TypeString,
|
||||
ArgTypes: []ast.Type{ast.TypeMap},
|
||||
ReturnType: ast.TypeList,
|
||||
Callback: func(args []interface{}) (interface{}, error) {
|
||||
// Prefix must include ending dot to be a map
|
||||
prefix := fmt.Sprintf("var.%s.", args[0].(string))
|
||||
keys := make([]string, 0, len(vs))
|
||||
for k, _ := range vs {
|
||||
if !strings.HasPrefix(k, prefix) {
|
||||
continue
|
||||
}
|
||||
keys = append(keys, k)
|
||||
}
|
||||
mapVar := args[0].(map[string]ast.Variable)
|
||||
keys := make([]string, 0)
|
||||
|
||||
if len(keys) <= 0 {
|
||||
return "", fmt.Errorf(
|
||||
"failed to find map '%s'",
|
||||
args[0].(string))
|
||||
for k, _ := range mapVar {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
vals := make([]string, 0, len(keys))
|
||||
|
||||
for _, k := range keys {
|
||||
v := vs[k]
|
||||
if v.Type != ast.TypeString {
|
||||
return "", fmt.Errorf("values(): %q has bad type %s", k, v.Type)
|
||||
values := make([]string, len(keys))
|
||||
for index, key := range keys {
|
||||
if value, ok := mapVar[key].Value.(string); ok {
|
||||
values[index] = value
|
||||
} else {
|
||||
return "", fmt.Errorf("values(): %q has element with bad type %s",
|
||||
key, mapVar[key].Type)
|
||||
}
|
||||
vals = append(vals, vs[k].Value.(string))
|
||||
}
|
||||
|
||||
return NewStringList(vals).String(), nil
|
||||
variable, err := hil.InterfaceToVariable(values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return variable.Value, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -672,28 +672,33 @@ func TestInterpolateFuncSplit(t *testing.T) {
|
|||
func TestInterpolateFuncLookup(t *testing.T) {
|
||||
testFunction(t, testFunctionConfig{
|
||||
Vars: map[string]ast.Variable{
|
||||
"var.foo.bar": ast.Variable{
|
||||
Value: "baz",
|
||||
"var.foo": ast.Variable{
|
||||
Type: ast.TypeMap,
|
||||
Value: map[string]ast.Variable{
|
||||
"bar": ast.Variable{
|
||||
Type: ast.TypeString,
|
||||
Value: "baz",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Cases: []testFunctionCase{
|
||||
{
|
||||
`${lookup("foo", "bar")}`,
|
||||
`${lookup(var.foo, "bar")}`,
|
||||
"baz",
|
||||
false,
|
||||
},
|
||||
|
||||
// Invalid key
|
||||
{
|
||||
`${lookup("foo", "baz")}`,
|
||||
`${lookup(var.foo, "baz")}`,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
|
||||
// Too many args
|
||||
{
|
||||
`${lookup("foo", "bar", "baz")}`,
|
||||
`${lookup(var.foo, "bar", "baz")}`,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
|
@ -704,14 +709,19 @@ func TestInterpolateFuncLookup(t *testing.T) {
|
|||
func TestInterpolateFuncKeys(t *testing.T) {
|
||||
testFunction(t, testFunctionConfig{
|
||||
Vars: map[string]ast.Variable{
|
||||
"var.foo.bar": ast.Variable{
|
||||
"var.foo": ast.Variable{
|
||||
Type: ast.TypeMap,
|
||||
Value: map[string]ast.Variable{
|
||||
"bar": ast.Variable{
|
||||
Value: "baz",
|
||||
Type: ast.TypeString,
|
||||
},
|
||||
"var.foo.qux": ast.Variable{
|
||||
"qux": ast.Variable{
|
||||
Value: "quack",
|
||||
Type: ast.TypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
"var.str": ast.Variable{
|
||||
Value: "astring",
|
||||
Type: ast.TypeString,
|
||||
|
@ -719,28 +729,28 @@ func TestInterpolateFuncKeys(t *testing.T) {
|
|||
},
|
||||
Cases: []testFunctionCase{
|
||||
{
|
||||
`${keys("foo")}`,
|
||||
NewStringList([]string{"bar", "qux"}).String(),
|
||||
`${keys(var.foo)}`,
|
||||
[]interface{}{"bar", "qux"},
|
||||
false,
|
||||
},
|
||||
|
||||
// Invalid key
|
||||
{
|
||||
`${keys("not")}`,
|
||||
`${keys(var.not)}`,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
|
||||
// Too many args
|
||||
{
|
||||
`${keys("foo", "bar")}`,
|
||||
`${keys(var.foo, "bar")}`,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
|
||||
// Not a map
|
||||
{
|
||||
`${keys("str")}`,
|
||||
`${keys(var.str)}`,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
|
@ -751,14 +761,19 @@ func TestInterpolateFuncKeys(t *testing.T) {
|
|||
func TestInterpolateFuncValues(t *testing.T) {
|
||||
testFunction(t, testFunctionConfig{
|
||||
Vars: map[string]ast.Variable{
|
||||
"var.foo.bar": ast.Variable{
|
||||
"var.foo": ast.Variable{
|
||||
Type: ast.TypeMap,
|
||||
Value: map[string]ast.Variable{
|
||||
"bar": ast.Variable{
|
||||
Value: "quack",
|
||||
Type: ast.TypeString,
|
||||
},
|
||||
"var.foo.qux": ast.Variable{
|
||||
"qux": ast.Variable{
|
||||
Value: "baz",
|
||||
Type: ast.TypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
"var.str": ast.Variable{
|
||||
Value: "astring",
|
||||
Type: ast.TypeString,
|
||||
|
@ -766,28 +781,28 @@ func TestInterpolateFuncValues(t *testing.T) {
|
|||
},
|
||||
Cases: []testFunctionCase{
|
||||
{
|
||||
`${values("foo")}`,
|
||||
NewStringList([]string{"quack", "baz"}).String(),
|
||||
`${values(var.foo)}`,
|
||||
[]interface{}{"quack", "baz"},
|
||||
false,
|
||||
},
|
||||
|
||||
// Invalid key
|
||||
{
|
||||
`${values("not")}`,
|
||||
`${values(var.not)}`,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
|
||||
// Too many args
|
||||
{
|
||||
`${values("foo", "bar")}`,
|
||||
`${values(var.foo, "bar")}`,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
|
||||
// Not a map
|
||||
{
|
||||
`${values("str")}`,
|
||||
`${values(var.str)}`,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
|
|
|
@ -42,7 +42,7 @@ type interpolationWalker struct {
|
|||
//
|
||||
// If Replace is set to false in interpolationWalker, then the replace
|
||||
// value can be anything as it will have no effect.
|
||||
type interpolationWalkerFunc func(ast.Node) (string, error)
|
||||
type interpolationWalkerFunc func(ast.Node) (interface{}, error)
|
||||
|
||||
// interpolationWalkerContextFunc is called by interpolationWalk if
|
||||
// ContextF is set. This receives both the interpolation and the location
|
||||
|
@ -150,8 +150,8 @@ func (w *interpolationWalker) Primitive(v reflect.Value) error {
|
|||
// set if it is computed. This behavior is different if we're
|
||||
// splitting (in a SliceElem) or not.
|
||||
remove := false
|
||||
if w.loc == reflectwalk.SliceElem && IsStringList(replaceVal) {
|
||||
parts := StringList(replaceVal).Slice()
|
||||
if w.loc == reflectwalk.SliceElem && IsStringList(replaceVal.(string)) {
|
||||
parts := StringList(replaceVal.(string)).Slice()
|
||||
for _, p := range parts {
|
||||
if p == UnknownVariableValue {
|
||||
remove = true
|
||||
|
|
|
@ -89,7 +89,7 @@ func TestInterpolationWalker_detect(t *testing.T) {
|
|||
|
||||
for i, tc := range cases {
|
||||
var actual []string
|
||||
detectFn := func(root ast.Node) (string, error) {
|
||||
detectFn := func(root ast.Node) (interface{}, error) {
|
||||
actual = append(actual, fmt.Sprintf("%s", root))
|
||||
return "", nil
|
||||
}
|
||||
|
@ -175,7 +175,7 @@ func TestInterpolationWalker_replace(t *testing.T) {
|
|||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
fn := func(ast.Node) (string, error) {
|
||||
fn := func(ast.Node) (interface{}, error) {
|
||||
return tc.Value, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -108,7 +108,7 @@ func (r *RawConfig) Interpolate(vs map[string]ast.Variable) error {
|
|||
defer r.lock.Unlock()
|
||||
|
||||
config := langEvalConfig(vs)
|
||||
return r.interpolate(func(root ast.Node) (string, error) {
|
||||
return r.interpolate(func(root ast.Node) (interface{}, error) {
|
||||
// We detect the variables again and check if the value of any
|
||||
// of the variables is the computed value. If it is, then we
|
||||
// treat this entire value as computed.
|
||||
|
@ -137,7 +137,7 @@ func (r *RawConfig) Interpolate(vs map[string]ast.Variable) error {
|
|||
return "", err
|
||||
}
|
||||
|
||||
return result.Value.(string), nil
|
||||
return result.Value, nil
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -194,7 +194,7 @@ func (r *RawConfig) init() error {
|
|||
r.Interpolations = nil
|
||||
r.Variables = nil
|
||||
|
||||
fn := func(node ast.Node) (string, error) {
|
||||
fn := func(node ast.Node) (interface{}, error) {
|
||||
r.Interpolations = append(r.Interpolations, node)
|
||||
vars, err := DetectVariables(node)
|
||||
if err != nil {
|
||||
|
|
|
@ -48,6 +48,45 @@ func TestContext2Apply(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestContext2Apply_mapVarBetweenModules(t *testing.T) {
|
||||
m := testModule(t, "apply-map-var-through-module")
|
||||
p := testProvider("null")
|
||||
p.ApplyFn = testApplyFn
|
||||
p.DiffFn = testDiffFn
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Module: m,
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"null": testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
if _, err := ctx.Plan(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
state, err := ctx.Apply()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(state.String())
|
||||
expected := strings.TrimSpace(`<no state>
|
||||
Outputs:
|
||||
|
||||
amis_from_module = {eu-west-1:ami-789012 eu-west-2:ami-989484 us-west-1:ami-123456 us-west-2:ami-456789 }
|
||||
|
||||
module.test:
|
||||
null_resource.noop:
|
||||
ID = foo
|
||||
|
||||
Outputs:
|
||||
|
||||
amis_out = {eu-west-1:ami-789012 eu-west-2:ami-989484 us-west-1:ami-123456 us-west-2:ami-456789 }`)
|
||||
if actual != expected {
|
||||
t.Fatalf("expected: \n%s\n\ngot: \n%s\n", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Apply_providerAlias(t *testing.T) {
|
||||
m := testModule(t, "apply-provider-alias")
|
||||
p := testProvider("aws")
|
||||
|
@ -3066,7 +3105,7 @@ func TestContext2Apply_outputInvalid(t *testing.T) {
|
|||
if err == nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "is not a string") {
|
||||
if !strings.Contains(err.Error(), "is not a valid type") {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
@ -3144,7 +3183,7 @@ func TestContext2Apply_outputList(t *testing.T) {
|
|||
actual := strings.TrimSpace(state.String())
|
||||
expected := strings.TrimSpace(testTerraformApplyOutputListStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad: \n%s", actual)
|
||||
t.Fatalf("expected: \n%s\n\nbad: \n%s", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3850,7 +3889,7 @@ func TestContext2Apply_vars(t *testing.T) {
|
|||
actual := strings.TrimSpace(state.String())
|
||||
expected := strings.TrimSpace(testTerraformApplyVarsStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad: \n%s", actual)
|
||||
t.Fatalf("expected: %s\n got:\n%s", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ func TestContext2Input(t *testing.T) {
|
|||
actual := strings.TrimSpace(state.String())
|
||||
expected := strings.TrimSpace(testTerraformInputVarsStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad: \n%s", actual)
|
||||
t.Fatalf("expected:\n%s\ngot:\n%s", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ type EvalContext interface {
|
|||
// SetVariables sets the variables for the module within
|
||||
// this context with the name n. This function call is additive:
|
||||
// the second parameter is merged with any previous call.
|
||||
SetVariables(string, map[string]string)
|
||||
SetVariables(string, map[string]interface{})
|
||||
|
||||
// Diff returns the global diff as well as the lock that should
|
||||
// be used to modify that diff.
|
||||
|
|
|
@ -23,7 +23,7 @@ type BuiltinEvalContext struct {
|
|||
// as the Interpolater itself, it is protected by InterpolaterVarLock
|
||||
// which must be locked during any access to the map.
|
||||
Interpolater *Interpolater
|
||||
InterpolaterVars map[string]map[string]string
|
||||
InterpolaterVars map[string]map[string]interface{}
|
||||
InterpolaterVarLock *sync.Mutex
|
||||
|
||||
Hooks []Hook
|
||||
|
@ -311,7 +311,7 @@ func (ctx *BuiltinEvalContext) Path() []string {
|
|||
return ctx.PathValue
|
||||
}
|
||||
|
||||
func (ctx *BuiltinEvalContext) SetVariables(n string, vs map[string]string) {
|
||||
func (ctx *BuiltinEvalContext) SetVariables(n string, vs map[string]interface{}) {
|
||||
ctx.InterpolaterVarLock.Lock()
|
||||
defer ctx.InterpolaterVarLock.Unlock()
|
||||
|
||||
|
@ -322,7 +322,7 @@ func (ctx *BuiltinEvalContext) SetVariables(n string, vs map[string]string) {
|
|||
|
||||
vars := ctx.InterpolaterVars[key]
|
||||
if vars == nil {
|
||||
vars = make(map[string]string)
|
||||
vars = make(map[string]interface{})
|
||||
ctx.InterpolaterVars[key] = vars
|
||||
}
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@ type MockEvalContext struct {
|
|||
|
||||
SetVariablesCalled bool
|
||||
SetVariablesModule string
|
||||
SetVariablesVariables map[string]string
|
||||
SetVariablesVariables map[string]interface{}
|
||||
|
||||
DiffCalled bool
|
||||
DiffDiff *Diff
|
||||
|
@ -183,7 +183,7 @@ func (c *MockEvalContext) Path() []string {
|
|||
return c.PathPath
|
||||
}
|
||||
|
||||
func (c *MockEvalContext) SetVariables(n string, vs map[string]string) {
|
||||
func (c *MockEvalContext) SetVariables(n string, vs map[string]interface{}) {
|
||||
c.SetVariablesCalled = true
|
||||
c.SetVariablesModule = n
|
||||
c.SetVariablesVariables = vs
|
||||
|
|
|
@ -2,6 +2,7 @@ package terraform
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
)
|
||||
|
@ -45,7 +46,8 @@ type EvalWriteOutput struct {
|
|||
func (n *EvalWriteOutput) Eval(ctx EvalContext) (interface{}, error) {
|
||||
cfg, err := ctx.Interpolate(n.Value, nil)
|
||||
if err != nil {
|
||||
// Ignore it
|
||||
// Log error but continue anyway
|
||||
log.Printf("[WARN] Output interpolation %q failed: %s", n.Name, err)
|
||||
}
|
||||
|
||||
state, lock := ctx.State()
|
||||
|
@ -76,16 +78,16 @@ func (n *EvalWriteOutput) Eval(ctx EvalContext) (interface{}, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// If it is a list of values, get the first one
|
||||
if list, ok := valueRaw.([]interface{}); ok {
|
||||
valueRaw = list[0]
|
||||
switch valueTyped := valueRaw.(type) {
|
||||
case string:
|
||||
mod.Outputs[n.Name] = valueTyped
|
||||
case []interface{}:
|
||||
mod.Outputs[n.Name] = valueTyped
|
||||
case map[string]interface{}:
|
||||
mod.Outputs[n.Name] = valueTyped
|
||||
default:
|
||||
return nil, fmt.Errorf("output %s is not a valid type (%T)\n", n.Name, valueTyped)
|
||||
}
|
||||
if _, ok := valueRaw.(string); !ok {
|
||||
return nil, fmt.Errorf("output %s is not a string", n.Name)
|
||||
}
|
||||
|
||||
// Write the output
|
||||
mod.Outputs[n.Name] = valueRaw.(string)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/config/module"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
|
@ -26,7 +25,7 @@ import (
|
|||
// 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]string
|
||||
Variables map[string]interface{}
|
||||
ModulePath []string
|
||||
ModuleTree *module.Tree
|
||||
}
|
||||
|
@ -43,12 +42,18 @@ func (n *EvalTypeCheckVariable) Eval(ctx EvalContext) (interface{}, error) {
|
|||
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.
|
||||
_, ok := n.Variables[name]
|
||||
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.
|
||||
|
@ -59,13 +64,23 @@ func (n *EvalTypeCheckVariable) Eval(ctx EvalContext) (interface{}, error) {
|
|||
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:
|
||||
// Only display a module 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 = ""
|
||||
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",
|
||||
|
@ -80,7 +95,7 @@ func (n *EvalTypeCheckVariable) Eval(ctx EvalContext) (interface{}, error) {
|
|||
// explicitly for interpolation later.
|
||||
type EvalSetVariables struct {
|
||||
Module *string
|
||||
Variables map[string]string
|
||||
Variables map[string]interface{}
|
||||
}
|
||||
|
||||
// TODO: test
|
||||
|
@ -94,30 +109,42 @@ func (n *EvalSetVariables) Eval(ctx EvalContext) (interface{}, error) {
|
|||
// mapping.
|
||||
type EvalVariableBlock struct {
|
||||
Config **ResourceConfig
|
||||
Variables map[string]string
|
||||
VariableValues map[string]interface{}
|
||||
}
|
||||
|
||||
// TODO: test
|
||||
func (n *EvalVariableBlock) Eval(ctx EvalContext) (interface{}, error) {
|
||||
// Clear out the existing mapping
|
||||
for k, _ := range n.Variables {
|
||||
delete(n.Variables, k)
|
||||
for k, _ := range n.VariableValues {
|
||||
delete(n.VariableValues, k)
|
||||
}
|
||||
|
||||
// Get our configuration
|
||||
rc := *n.Config
|
||||
for k, v := range rc.Config {
|
||||
var vStr string
|
||||
if err := mapstructure.WeakDecode(v, &vStr); err != nil {
|
||||
return nil, errwrap.Wrapf(fmt.Sprintf(
|
||||
"%s: error reading value: {{err}}", k), err)
|
||||
var vString string
|
||||
if err := mapstructure.WeakDecode(v, &vString); err == nil {
|
||||
n.VariableValues[k] = vString
|
||||
continue
|
||||
}
|
||||
|
||||
n.Variables[k] = vStr
|
||||
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.Variables[k]; !ok {
|
||||
n.Variables[k] = config.UnknownVariableValue
|
||||
if _, ok := n.VariableValues[k]; !ok {
|
||||
n.VariableValues[k] = config.UnknownVariableValue
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@ func (n *GraphNodeConfigModule) Expand(b GraphBuilder) (GraphNodeSubgraph, error
|
|||
return &graphNodeModuleExpanded{
|
||||
Original: n,
|
||||
Graph: graph,
|
||||
Variables: make(map[string]string),
|
||||
Variables: make(map[string]interface{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -107,7 +107,7 @@ type graphNodeModuleExpanded struct {
|
|||
// Variables is a map of the input variables. This reference should
|
||||
// be shared with ModuleInputTransformer in order to create a connection
|
||||
// where the variables are set properly.
|
||||
Variables map[string]string
|
||||
Variables map[string]interface{}
|
||||
}
|
||||
|
||||
func (n *graphNodeModuleExpanded) Name() string {
|
||||
|
@ -148,7 +148,7 @@ func (n *graphNodeModuleExpanded) EvalTree() EvalNode {
|
|||
|
||||
&EvalVariableBlock{
|
||||
Config: &resourceConfig,
|
||||
Variables: n.Variables,
|
||||
VariableValues: n.Variables,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -114,7 +114,7 @@ func (n *GraphNodeConfigVariable) EvalTree() EvalNode {
|
|||
// Otherwise, interpolate the value of this variable and set it
|
||||
// within the variables mapping.
|
||||
var config *ResourceConfig
|
||||
variables := make(map[string]string)
|
||||
variables := make(map[string]interface{})
|
||||
return &EvalSequence{
|
||||
Nodes: []EvalNode{
|
||||
&EvalInterpolate{
|
||||
|
@ -124,7 +124,7 @@ func (n *GraphNodeConfigVariable) EvalTree() EvalNode {
|
|||
|
||||
&EvalVariableBlock{
|
||||
Config: &config,
|
||||
Variables: variables,
|
||||
VariableValues: variables,
|
||||
},
|
||||
|
||||
&EvalTypeCheckVariable{
|
||||
|
|
|
@ -27,7 +27,7 @@ type ContextGraphWalker struct {
|
|||
once sync.Once
|
||||
contexts map[string]*BuiltinEvalContext
|
||||
contextLock sync.Mutex
|
||||
interpolaterVars map[string]map[string]string
|
||||
interpolaterVars map[string]map[string]interface{}
|
||||
interpolaterVarLock sync.Mutex
|
||||
providerCache map[string]ResourceProvider
|
||||
providerConfigCache map[string]*ResourceConfig
|
||||
|
@ -49,7 +49,7 @@ func (w *ContextGraphWalker) EnterPath(path []string) EvalContext {
|
|||
}
|
||||
|
||||
// Setup the variables for this interpolater
|
||||
variables := make(map[string]string)
|
||||
variables := make(map[string]interface{})
|
||||
if len(path) <= 1 {
|
||||
for k, v := range w.Context.variables {
|
||||
variables[k] = v
|
||||
|
@ -85,8 +85,8 @@ func (w *ContextGraphWalker) EnterPath(path []string) EvalContext {
|
|||
Module: w.Context.module,
|
||||
State: w.Context.state,
|
||||
StateLock: &w.Context.stateLock,
|
||||
Variables: variables,
|
||||
VariablesLock: &w.interpolaterVarLock,
|
||||
VariableValues: variables,
|
||||
VariableValuesLock: &w.interpolaterVarLock,
|
||||
},
|
||||
InterpolaterVars: w.interpolaterVars,
|
||||
InterpolaterVarLock: &w.interpolaterVarLock,
|
||||
|
@ -150,5 +150,5 @@ func (w *ContextGraphWalker) init() {
|
|||
w.providerCache = make(map[string]ResourceProvider, 5)
|
||||
w.providerConfigCache = make(map[string]*ResourceConfig, 5)
|
||||
w.provisionerCache = make(map[string]ResourceProvisioner, 5)
|
||||
w.interpolaterVars = make(map[string]map[string]string, 5)
|
||||
w.interpolaterVars = make(map[string]map[string]interface{}, 5)
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/hil"
|
||||
"github.com/hashicorp/hil/ast"
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/config/module"
|
||||
|
@ -27,8 +28,8 @@ type Interpolater struct {
|
|||
Module *module.Tree
|
||||
State *State
|
||||
StateLock *sync.RWMutex
|
||||
Variables map[string]string
|
||||
VariablesLock *sync.Mutex
|
||||
VariableValues map[string]interface{}
|
||||
VariableValuesLock *sync.Mutex
|
||||
}
|
||||
|
||||
// InterpolationScope is the current scope of execution. This is required
|
||||
|
@ -52,12 +53,18 @@ func (i *Interpolater) Values(
|
|||
mod = i.Module.Child(scope.Path[1:])
|
||||
}
|
||||
for _, v := range mod.Config().Variables {
|
||||
for k, val := range v.DefaultsMap() {
|
||||
result[k] = ast.Variable{
|
||||
Value: val,
|
||||
Type: ast.TypeString,
|
||||
// Set default variables
|
||||
if v.Default == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
n := fmt.Sprintf("var.%s", v.Name)
|
||||
variable, err := hil.InterfaceToVariable(v.Default)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid default map value for %s: %v", v.Name, v.Default)
|
||||
}
|
||||
// Potentially TODO(jen20): check against declared type
|
||||
result[n] = variable
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,18 +117,6 @@ func (i *Interpolater) valueCountVar(
|
|||
}
|
||||
}
|
||||
|
||||
func interfaceToHILVariable(input interface{}) ast.Variable {
|
||||
switch v := input.(type) {
|
||||
case string:
|
||||
return ast.Variable{
|
||||
Type: ast.TypeString,
|
||||
Value: v,
|
||||
}
|
||||
default:
|
||||
panic(fmt.Errorf("Unknown interface type %T in interfaceToHILVariable", v))
|
||||
}
|
||||
}
|
||||
|
||||
func unknownVariable() ast.Variable {
|
||||
return ast.Variable{
|
||||
Type: ast.TypeString,
|
||||
|
@ -167,7 +162,11 @@ func (i *Interpolater) valueModuleVar(
|
|||
} else {
|
||||
// Get the value from the outputs
|
||||
if value, ok := mod.Outputs[v.Field]; ok {
|
||||
result[n] = interfaceToHILVariable(value)
|
||||
output, err := hil.InterfaceToVariable(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result[n] = output
|
||||
} else {
|
||||
// Same reasons as the comment above.
|
||||
result[n] = unknownVariable()
|
||||
|
@ -289,33 +288,44 @@ func (i *Interpolater) valueUserVar(
|
|||
n string,
|
||||
v *config.UserVariable,
|
||||
result map[string]ast.Variable) error {
|
||||
i.VariablesLock.Lock()
|
||||
defer i.VariablesLock.Unlock()
|
||||
val, ok := i.Variables[v.Name]
|
||||
i.VariableValuesLock.Lock()
|
||||
defer i.VariableValuesLock.Unlock()
|
||||
val, ok := i.VariableValues[v.Name]
|
||||
if ok {
|
||||
result[n] = ast.Variable{
|
||||
Value: val,
|
||||
Type: ast.TypeString,
|
||||
varValue, err := hil.InterfaceToVariable(val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot convert %s value %q to an ast.Variable for interpolation: %s",
|
||||
v.Name, val, err)
|
||||
}
|
||||
result[n] = varValue
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, ok := result[n]; !ok && i.Operation == walkValidate {
|
||||
result[n] = ast.Variable{
|
||||
Value: config.UnknownVariableValue,
|
||||
Type: ast.TypeString,
|
||||
}
|
||||
result[n] = unknownVariable()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Look up if we have any variables with this prefix because
|
||||
// those are map overrides. Include those.
|
||||
for k, val := range i.Variables {
|
||||
for k, val := range i.VariableValues {
|
||||
if strings.HasPrefix(k, v.Name+".") {
|
||||
result["var."+k] = ast.Variable{
|
||||
Value: val,
|
||||
Type: ast.TypeString,
|
||||
keyComponents := strings.Split(k, ".")
|
||||
overrideKey := keyComponents[len(keyComponents)-1]
|
||||
|
||||
mapInterface, ok := result["var."+v.Name]
|
||||
if !ok {
|
||||
return fmt.Errorf("override for non-existent variable: %s", v.Name)
|
||||
}
|
||||
|
||||
mapVariable := mapInterface.Value.(map[string]ast.Variable)
|
||||
|
||||
varValue, err := hil.InterfaceToVariable(val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot convert %s value %q to an ast.Variable for interpolation: %s",
|
||||
v.Name, val, err)
|
||||
}
|
||||
mapVariable[overrideKey] = varValue
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -575,7 +575,7 @@ func (m *ModuleState) Equal(other *ModuleState) bool {
|
|||
return false
|
||||
}
|
||||
for k, v := range m.Outputs {
|
||||
if other.Outputs[k] != v {
|
||||
if !reflect.DeepEqual(other.Outputs[k], v) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -803,7 +803,27 @@ func (m *ModuleState) String() string {
|
|||
|
||||
for _, k := range ks {
|
||||
v := m.Outputs[k]
|
||||
buf.WriteString(fmt.Sprintf("%s = %s\n", k, v))
|
||||
switch vTyped := v.(type) {
|
||||
case string:
|
||||
buf.WriteString(fmt.Sprintf("%s = %s\n", k, vTyped))
|
||||
case []interface{}:
|
||||
buf.WriteString(fmt.Sprintf("%s = %s\n", k, vTyped))
|
||||
case map[string]interface{}:
|
||||
var mapKeys []string
|
||||
for key, _ := range vTyped {
|
||||
mapKeys = append(mapKeys, key)
|
||||
}
|
||||
sort.Strings(mapKeys)
|
||||
|
||||
var mapBuf bytes.Buffer
|
||||
mapBuf.WriteString("{")
|
||||
for _, key := range mapKeys {
|
||||
mapBuf.WriteString(fmt.Sprintf("%s:%s ", key, vTyped[key]))
|
||||
}
|
||||
mapBuf.WriteString("}")
|
||||
|
||||
buf.WriteString(fmt.Sprintf("%s = %s\n", k, mapBuf.String()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -592,7 +592,7 @@ aws_instance.foo:
|
|||
|
||||
Outputs:
|
||||
|
||||
foo_num = bar,bar,bar
|
||||
foo_num = [bar,bar,bar]
|
||||
`
|
||||
|
||||
const testTerraformApplyOutputMultiStr = `
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
variable "amis" {
|
||||
type = "map"
|
||||
}
|
||||
|
||||
resource "null_resource" "noop" {}
|
||||
|
||||
output "amis_out" {
|
||||
value = "${var.amis}"
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
variable "amis_in" {
|
||||
type = "map"
|
||||
default = {
|
||||
"us-west-1" = "ami-123456"
|
||||
"us-west-2" = "ami-456789"
|
||||
"eu-west-1" = "ami-789012"
|
||||
"eu-west-2" = "ami-989484"
|
||||
}
|
||||
}
|
||||
|
||||
module "test" {
|
||||
source = "./amodule"
|
||||
|
||||
amis = "${var.amis_in}"
|
||||
}
|
||||
|
||||
output "amis_from_module" {
|
||||
value = "${module.test.amis_out}"
|
||||
}
|
|
@ -19,5 +19,5 @@ resource "aws_instance" "foo" {
|
|||
resource "aws_instance" "bar" {
|
||||
foo = "${var.foo}"
|
||||
bar = "${lookup(var.amis, var.foo)}"
|
||||
baz = "${var.amis.us-east-1}"
|
||||
baz = "${var.amis["us-east-1"]}"
|
||||
}
|
||||
|
|
|
@ -122,6 +122,7 @@ support for the "us-west-2" region as well:
|
|||
|
||||
```
|
||||
variable "amis" {
|
||||
type = "map"
|
||||
default = {
|
||||
us-east-1 = "ami-b8b061d0"
|
||||
us-west-2 = "ami-ef5e24df"
|
||||
|
@ -129,8 +130,8 @@ variable "amis" {
|
|||
}
|
||||
```
|
||||
|
||||
A variable becomes a mapping when it has a default value that is a
|
||||
map like above. There is no way to create a required map.
|
||||
A variable becomes a mapping when it has a type of "map" assigned, or has a
|
||||
default value that is a map like above.
|
||||
|
||||
Then, replace the "aws\_instance" with the following:
|
||||
|
||||
|
@ -148,7 +149,7 @@ variables is the key.
|
|||
|
||||
While we don't use it in our example, it is worth noting that you
|
||||
can also do a static lookup of a mapping directly with
|
||||
`${var.amis.us-east-1}`.
|
||||
`${var.amis["us-east-1"]}`.
|
||||
|
||||
<a id="assigning-mappings"></a>
|
||||
## Assigning Mappings
|
||||
|
|
Loading…
Reference in New Issue