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
|
// Output the outputs in alphabetical order
|
||||||
keyLen := 0
|
keyLen := 0
|
||||||
keys := make([]string, 0, len(outputs))
|
ks := make([]string, 0, len(outputs))
|
||||||
for key, _ := range outputs {
|
for key, _ := range outputs {
|
||||||
keys = append(keys, key)
|
ks = append(ks, key)
|
||||||
if len(key) > keyLen {
|
if len(key) > keyLen {
|
||||||
keyLen = len(key)
|
keyLen = len(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sort.Strings(keys)
|
sort.Strings(ks)
|
||||||
|
|
||||||
for _, k := range keys {
|
|
||||||
v := outputs[k]
|
|
||||||
|
|
||||||
|
for _, k := range ks {
|
||||||
if schemaMap[k].Sensitive {
|
if schemaMap[k].Sensitive {
|
||||||
outputBuf.WriteString(fmt.Sprintf(
|
outputBuf.WriteString(fmt.Sprintf("%s = <sensitive>\n", k))
|
||||||
" %s%s = <sensitive>\n",
|
continue
|
||||||
k,
|
}
|
||||||
strings.Repeat(" ", keyLen-len(k))))
|
|
||||||
} else {
|
v := outputs[k]
|
||||||
outputBuf.WriteString(fmt.Sprintf(
|
switch typedV := v.(type) {
|
||||||
" %s%s = %s\n",
|
case string:
|
||||||
k,
|
outputBuf.WriteString(fmt.Sprintf("%s = %s\n", k, typedV))
|
||||||
strings.Repeat(" ", keyLen-len(k)),
|
case []interface{}:
|
||||||
v))
|
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
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,7 +29,7 @@ func (c *OutputCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
args = cmdFlags.Args()
|
args = cmdFlags.Args()
|
||||||
if len(args) > 1 {
|
if len(args) > 2 {
|
||||||
c.Ui.Error(
|
c.Ui.Error(
|
||||||
"The output command expects exactly one argument with the name\n" +
|
"The output command expects exactly one argument with the name\n" +
|
||||||
"of an output variable or no arguments to show all outputs.\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]
|
name = args[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
index := ""
|
||||||
|
if len(args) > 1 {
|
||||||
|
index = args[1]
|
||||||
|
}
|
||||||
|
|
||||||
stateStore, err := c.Meta.State()
|
stateStore, err := c.Meta.State()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Error reading state: %s", err))
|
c.Ui.Error(fmt.Sprintf("Error reading state: %s", err))
|
||||||
|
@ -74,17 +81,7 @@ func (c *OutputCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
if name == "" {
|
if name == "" {
|
||||||
ks := make([]string, 0, len(mod.Outputs))
|
c.Ui.Output(outputsAsString(state))
|
||||||
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))
|
|
||||||
}
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,6 +98,44 @@ func (c *OutputCommand) Run(args []string) int {
|
||||||
switch output := v.(type) {
|
switch output := v.(type) {
|
||||||
case string:
|
case string:
|
||||||
c.Ui.Output(output)
|
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:
|
default:
|
||||||
panic(fmt.Errorf("Unknown output type: %T", output))
|
panic(fmt.Errorf("Unknown output type: %T", output))
|
||||||
}
|
}
|
||||||
|
@ -108,6 +143,53 @@ func (c *OutputCommand) Run(args []string) int {
|
||||||
return 0
|
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 {
|
func (c *OutputCommand) Help() string {
|
||||||
helpText := `
|
helpText := `
|
||||||
Usage: terraform output [options] [NAME]
|
Usage: terraform output [options] [NAME]
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
"github.com/hashicorp/hil"
|
"github.com/hashicorp/hil"
|
||||||
"github.com/hashicorp/hil/ast"
|
"github.com/hashicorp/hil/ast"
|
||||||
"github.com/hashicorp/terraform/flatmap"
|
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
"github.com/mitchellh/reflectwalk"
|
"github.com/mitchellh/reflectwalk"
|
||||||
)
|
)
|
||||||
|
@ -239,7 +238,7 @@ func (c *Config) Validate() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
interp := false
|
interp := false
|
||||||
fn := func(ast.Node) (string, error) {
|
fn := func(ast.Node) (interface{}, error) {
|
||||||
interp = true
|
interp = true
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
@ -450,7 +449,7 @@ func (c *Config) Validate() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interpolate with a fixed number to verify that its a number.
|
// 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
|
// Execute the node but transform the AST so that it returns
|
||||||
// a fixed value of "5" for all interpolations.
|
// a fixed value of "5" for all interpolations.
|
||||||
result, err := hil.Eval(
|
result, err := hil.Eval(
|
||||||
|
@ -461,7 +460,7 @@ func (c *Config) Validate() error {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.Value.(string), nil
|
return result.Value, nil
|
||||||
})
|
})
|
||||||
_, err := strconv.ParseInt(r.RawCount.Value().(string), 0, 0)
|
_, err := strconv.ParseInt(r.RawCount.Value().(string), 0, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -809,28 +808,6 @@ func (r *Resource) mergerMerge(m merger) merger {
|
||||||
return &result
|
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.
|
// Merge merges two variables to create a new third variable.
|
||||||
func (v *Variable) Merge(v2 *Variable) *Variable {
|
func (v *Variable) Merge(v2 *Variable) *Variable {
|
||||||
// Shallow copy the variable
|
// Shallow copy the variable
|
||||||
|
|
|
@ -2,7 +2,6 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"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 {
|
func testConfig(t *testing.T, name string) *Config {
|
||||||
c, err := LoadFile(filepath.Join(fixtureDir, name, "main.tf"))
|
c, err := LoadFile(filepath.Join(fixtureDir, name, "main.tf"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
|
|
||||||
"github.com/apparentlymart/go-cidr/cidr"
|
"github.com/apparentlymart/go-cidr/cidr"
|
||||||
"github.com/hashicorp/go-uuid"
|
"github.com/hashicorp/go-uuid"
|
||||||
|
"github.com/hashicorp/hil"
|
||||||
"github.com/hashicorp/hil/ast"
|
"github.com/hashicorp/hil/ast"
|
||||||
"github.com/mitchellh/go-homedir"
|
"github.com/mitchellh/go-homedir"
|
||||||
)
|
)
|
||||||
|
@ -466,20 +467,22 @@ func interpolationFuncSplit() ast.Function {
|
||||||
// dynamic lookups of map types within a Terraform configuration.
|
// dynamic lookups of map types within a Terraform configuration.
|
||||||
func interpolationFuncLookup(vs map[string]ast.Variable) ast.Function {
|
func interpolationFuncLookup(vs map[string]ast.Variable) ast.Function {
|
||||||
return ast.Function{
|
return ast.Function{
|
||||||
ArgTypes: []ast.Type{ast.TypeString, ast.TypeString},
|
ArgTypes: []ast.Type{ast.TypeMap, ast.TypeString},
|
||||||
ReturnType: ast.TypeString,
|
ReturnType: ast.TypeString,
|
||||||
Callback: func(args []interface{}) (interface{}, error) {
|
Callback: func(args []interface{}) (interface{}, error) {
|
||||||
k := fmt.Sprintf("var.%s.%s", args[0].(string), args[1].(string))
|
index := args[1].(string)
|
||||||
v, ok := vs[k]
|
mapVar := args[0].(map[string]ast.Variable)
|
||||||
|
|
||||||
|
v, ok := mapVar[index]
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", fmt.Errorf(
|
return "", fmt.Errorf(
|
||||||
"lookup in '%s' failed to find '%s'",
|
"lookup failed to find '%s'",
|
||||||
args[0].(string), args[1].(string))
|
args[1].(string))
|
||||||
}
|
}
|
||||||
if v.Type != ast.TypeString {
|
if v.Type != ast.TypeString {
|
||||||
return "", fmt.Errorf(
|
return "", fmt.Errorf(
|
||||||
"lookup in '%s' for '%s' has bad type %s",
|
"lookup for '%s' has bad type %s",
|
||||||
args[0].(string), args[1].(string), v.Type)
|
args[1].(string), v.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
return v.Value.(string), nil
|
return v.Value.(string), nil
|
||||||
|
@ -513,28 +516,24 @@ func interpolationFuncElement() ast.Function {
|
||||||
// keys of map types within a Terraform configuration.
|
// keys of map types within a Terraform configuration.
|
||||||
func interpolationFuncKeys(vs map[string]ast.Variable) ast.Function {
|
func interpolationFuncKeys(vs map[string]ast.Variable) ast.Function {
|
||||||
return ast.Function{
|
return ast.Function{
|
||||||
ArgTypes: []ast.Type{ast.TypeString},
|
ArgTypes: []ast.Type{ast.TypeMap},
|
||||||
ReturnType: ast.TypeString,
|
ReturnType: ast.TypeList,
|
||||||
Callback: func(args []interface{}) (interface{}, error) {
|
Callback: func(args []interface{}) (interface{}, error) {
|
||||||
// Prefix must include ending dot to be a map
|
mapVar := args[0].(map[string]ast.Variable)
|
||||||
prefix := fmt.Sprintf("var.%s.", args[0].(string))
|
keys := make([]string, 0)
|
||||||
keys := make([]string, 0, len(vs))
|
|
||||||
for k, _ := range vs {
|
|
||||||
if !strings.HasPrefix(k, prefix) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
keys = append(keys, k[len(prefix):])
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(keys) <= 0 {
|
for k, _ := range mapVar {
|
||||||
return "", fmt.Errorf(
|
keys = append(keys, k)
|
||||||
"failed to find map '%s'",
|
|
||||||
args[0].(string))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Strings(keys)
|
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.
|
// keys of map types within a Terraform configuration.
|
||||||
func interpolationFuncValues(vs map[string]ast.Variable) ast.Function {
|
func interpolationFuncValues(vs map[string]ast.Variable) ast.Function {
|
||||||
return ast.Function{
|
return ast.Function{
|
||||||
ArgTypes: []ast.Type{ast.TypeString},
|
ArgTypes: []ast.Type{ast.TypeMap},
|
||||||
ReturnType: ast.TypeString,
|
ReturnType: ast.TypeList,
|
||||||
Callback: func(args []interface{}) (interface{}, error) {
|
Callback: func(args []interface{}) (interface{}, error) {
|
||||||
// Prefix must include ending dot to be a map
|
mapVar := args[0].(map[string]ast.Variable)
|
||||||
prefix := fmt.Sprintf("var.%s.", args[0].(string))
|
keys := make([]string, 0)
|
||||||
keys := make([]string, 0, len(vs))
|
|
||||||
for k, _ := range vs {
|
|
||||||
if !strings.HasPrefix(k, prefix) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
keys = append(keys, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(keys) <= 0 {
|
for k, _ := range mapVar {
|
||||||
return "", fmt.Errorf(
|
keys = append(keys, k)
|
||||||
"failed to find map '%s'",
|
|
||||||
args[0].(string))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Strings(keys)
|
sort.Strings(keys)
|
||||||
|
|
||||||
vals := make([]string, 0, len(keys))
|
values := make([]string, len(keys))
|
||||||
|
for index, key := range keys {
|
||||||
for _, k := range keys {
|
if value, ok := mapVar[key].Value.(string); ok {
|
||||||
v := vs[k]
|
values[index] = value
|
||||||
if v.Type != ast.TypeString {
|
} else {
|
||||||
return "", fmt.Errorf("values(): %q has bad type %s", k, v.Type)
|
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) {
|
func TestInterpolateFuncLookup(t *testing.T) {
|
||||||
testFunction(t, testFunctionConfig{
|
testFunction(t, testFunctionConfig{
|
||||||
Vars: map[string]ast.Variable{
|
Vars: map[string]ast.Variable{
|
||||||
"var.foo.bar": ast.Variable{
|
"var.foo": ast.Variable{
|
||||||
Value: "baz",
|
Type: ast.TypeMap,
|
||||||
|
Value: map[string]ast.Variable{
|
||||||
|
"bar": ast.Variable{
|
||||||
Type: ast.TypeString,
|
Type: ast.TypeString,
|
||||||
|
Value: "baz",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Cases: []testFunctionCase{
|
Cases: []testFunctionCase{
|
||||||
{
|
{
|
||||||
`${lookup("foo", "bar")}`,
|
`${lookup(var.foo, "bar")}`,
|
||||||
"baz",
|
"baz",
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Invalid key
|
// Invalid key
|
||||||
{
|
{
|
||||||
`${lookup("foo", "baz")}`,
|
`${lookup(var.foo, "baz")}`,
|
||||||
nil,
|
nil,
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Too many args
|
// Too many args
|
||||||
{
|
{
|
||||||
`${lookup("foo", "bar", "baz")}`,
|
`${lookup(var.foo, "bar", "baz")}`,
|
||||||
nil,
|
nil,
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
|
@ -704,14 +709,19 @@ func TestInterpolateFuncLookup(t *testing.T) {
|
||||||
func TestInterpolateFuncKeys(t *testing.T) {
|
func TestInterpolateFuncKeys(t *testing.T) {
|
||||||
testFunction(t, testFunctionConfig{
|
testFunction(t, testFunctionConfig{
|
||||||
Vars: map[string]ast.Variable{
|
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",
|
Value: "baz",
|
||||||
Type: ast.TypeString,
|
Type: ast.TypeString,
|
||||||
},
|
},
|
||||||
"var.foo.qux": ast.Variable{
|
"qux": ast.Variable{
|
||||||
Value: "quack",
|
Value: "quack",
|
||||||
Type: ast.TypeString,
|
Type: ast.TypeString,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
"var.str": ast.Variable{
|
"var.str": ast.Variable{
|
||||||
Value: "astring",
|
Value: "astring",
|
||||||
Type: ast.TypeString,
|
Type: ast.TypeString,
|
||||||
|
@ -719,28 +729,28 @@ func TestInterpolateFuncKeys(t *testing.T) {
|
||||||
},
|
},
|
||||||
Cases: []testFunctionCase{
|
Cases: []testFunctionCase{
|
||||||
{
|
{
|
||||||
`${keys("foo")}`,
|
`${keys(var.foo)}`,
|
||||||
NewStringList([]string{"bar", "qux"}).String(),
|
[]interface{}{"bar", "qux"},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Invalid key
|
// Invalid key
|
||||||
{
|
{
|
||||||
`${keys("not")}`,
|
`${keys(var.not)}`,
|
||||||
nil,
|
nil,
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Too many args
|
// Too many args
|
||||||
{
|
{
|
||||||
`${keys("foo", "bar")}`,
|
`${keys(var.foo, "bar")}`,
|
||||||
nil,
|
nil,
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Not a map
|
// Not a map
|
||||||
{
|
{
|
||||||
`${keys("str")}`,
|
`${keys(var.str)}`,
|
||||||
nil,
|
nil,
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
|
@ -751,14 +761,19 @@ func TestInterpolateFuncKeys(t *testing.T) {
|
||||||
func TestInterpolateFuncValues(t *testing.T) {
|
func TestInterpolateFuncValues(t *testing.T) {
|
||||||
testFunction(t, testFunctionConfig{
|
testFunction(t, testFunctionConfig{
|
||||||
Vars: map[string]ast.Variable{
|
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",
|
Value: "quack",
|
||||||
Type: ast.TypeString,
|
Type: ast.TypeString,
|
||||||
},
|
},
|
||||||
"var.foo.qux": ast.Variable{
|
"qux": ast.Variable{
|
||||||
Value: "baz",
|
Value: "baz",
|
||||||
Type: ast.TypeString,
|
Type: ast.TypeString,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
"var.str": ast.Variable{
|
"var.str": ast.Variable{
|
||||||
Value: "astring",
|
Value: "astring",
|
||||||
Type: ast.TypeString,
|
Type: ast.TypeString,
|
||||||
|
@ -766,28 +781,28 @@ func TestInterpolateFuncValues(t *testing.T) {
|
||||||
},
|
},
|
||||||
Cases: []testFunctionCase{
|
Cases: []testFunctionCase{
|
||||||
{
|
{
|
||||||
`${values("foo")}`,
|
`${values(var.foo)}`,
|
||||||
NewStringList([]string{"quack", "baz"}).String(),
|
[]interface{}{"quack", "baz"},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Invalid key
|
// Invalid key
|
||||||
{
|
{
|
||||||
`${values("not")}`,
|
`${values(var.not)}`,
|
||||||
nil,
|
nil,
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Too many args
|
// Too many args
|
||||||
{
|
{
|
||||||
`${values("foo", "bar")}`,
|
`${values(var.foo, "bar")}`,
|
||||||
nil,
|
nil,
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Not a map
|
// Not a map
|
||||||
{
|
{
|
||||||
`${values("str")}`,
|
`${values(var.str)}`,
|
||||||
nil,
|
nil,
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
|
|
|
@ -42,7 +42,7 @@ type interpolationWalker struct {
|
||||||
//
|
//
|
||||||
// If Replace is set to false in interpolationWalker, then the replace
|
// If Replace is set to false in interpolationWalker, then the replace
|
||||||
// value can be anything as it will have no effect.
|
// 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
|
// interpolationWalkerContextFunc is called by interpolationWalk if
|
||||||
// ContextF is set. This receives both the interpolation and the location
|
// 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
|
// set if it is computed. This behavior is different if we're
|
||||||
// splitting (in a SliceElem) or not.
|
// splitting (in a SliceElem) or not.
|
||||||
remove := false
|
remove := false
|
||||||
if w.loc == reflectwalk.SliceElem && IsStringList(replaceVal) {
|
if w.loc == reflectwalk.SliceElem && IsStringList(replaceVal.(string)) {
|
||||||
parts := StringList(replaceVal).Slice()
|
parts := StringList(replaceVal.(string)).Slice()
|
||||||
for _, p := range parts {
|
for _, p := range parts {
|
||||||
if p == UnknownVariableValue {
|
if p == UnknownVariableValue {
|
||||||
remove = true
|
remove = true
|
||||||
|
|
|
@ -89,7 +89,7 @@ func TestInterpolationWalker_detect(t *testing.T) {
|
||||||
|
|
||||||
for i, tc := range cases {
|
for i, tc := range cases {
|
||||||
var actual []string
|
var actual []string
|
||||||
detectFn := func(root ast.Node) (string, error) {
|
detectFn := func(root ast.Node) (interface{}, error) {
|
||||||
actual = append(actual, fmt.Sprintf("%s", root))
|
actual = append(actual, fmt.Sprintf("%s", root))
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
@ -175,7 +175,7 @@ func TestInterpolationWalker_replace(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tc := range cases {
|
for i, tc := range cases {
|
||||||
fn := func(ast.Node) (string, error) {
|
fn := func(ast.Node) (interface{}, error) {
|
||||||
return tc.Value, nil
|
return tc.Value, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -108,7 +108,7 @@ func (r *RawConfig) Interpolate(vs map[string]ast.Variable) error {
|
||||||
defer r.lock.Unlock()
|
defer r.lock.Unlock()
|
||||||
|
|
||||||
config := langEvalConfig(vs)
|
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
|
// We detect the variables again and check if the value of any
|
||||||
// of the variables is the computed value. If it is, then we
|
// of the variables is the computed value. If it is, then we
|
||||||
// treat this entire value as computed.
|
// treat this entire value as computed.
|
||||||
|
@ -137,7 +137,7 @@ func (r *RawConfig) Interpolate(vs map[string]ast.Variable) error {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.Value.(string), nil
|
return result.Value, nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,7 +194,7 @@ func (r *RawConfig) init() error {
|
||||||
r.Interpolations = nil
|
r.Interpolations = nil
|
||||||
r.Variables = nil
|
r.Variables = nil
|
||||||
|
|
||||||
fn := func(node ast.Node) (string, error) {
|
fn := func(node ast.Node) (interface{}, error) {
|
||||||
r.Interpolations = append(r.Interpolations, node)
|
r.Interpolations = append(r.Interpolations, node)
|
||||||
vars, err := DetectVariables(node)
|
vars, err := DetectVariables(node)
|
||||||
if err != nil {
|
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) {
|
func TestContext2Apply_providerAlias(t *testing.T) {
|
||||||
m := testModule(t, "apply-provider-alias")
|
m := testModule(t, "apply-provider-alias")
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
|
@ -3066,7 +3105,7 @@ func TestContext2Apply_outputInvalid(t *testing.T) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("err: %s", err)
|
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)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3144,7 +3183,7 @@ func TestContext2Apply_outputList(t *testing.T) {
|
||||||
actual := strings.TrimSpace(state.String())
|
actual := strings.TrimSpace(state.String())
|
||||||
expected := strings.TrimSpace(testTerraformApplyOutputListStr)
|
expected := strings.TrimSpace(testTerraformApplyOutputListStr)
|
||||||
if actual != expected {
|
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())
|
actual := strings.TrimSpace(state.String())
|
||||||
expected := strings.TrimSpace(testTerraformApplyVarsStr)
|
expected := strings.TrimSpace(testTerraformApplyVarsStr)
|
||||||
if actual != expected {
|
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())
|
actual := strings.TrimSpace(state.String())
|
||||||
expected := strings.TrimSpace(testTerraformInputVarsStr)
|
expected := strings.TrimSpace(testTerraformInputVarsStr)
|
||||||
if actual != expected {
|
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
|
// SetVariables sets the variables for the module within
|
||||||
// this context with the name n. This function call is additive:
|
// this context with the name n. This function call is additive:
|
||||||
// the second parameter is merged with any previous call.
|
// 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
|
// Diff returns the global diff as well as the lock that should
|
||||||
// be used to modify that diff.
|
// be used to modify that diff.
|
||||||
|
|
|
@ -23,7 +23,7 @@ type BuiltinEvalContext struct {
|
||||||
// as the Interpolater itself, it is protected by InterpolaterVarLock
|
// as the Interpolater itself, it is protected by InterpolaterVarLock
|
||||||
// which must be locked during any access to the map.
|
// which must be locked during any access to the map.
|
||||||
Interpolater *Interpolater
|
Interpolater *Interpolater
|
||||||
InterpolaterVars map[string]map[string]string
|
InterpolaterVars map[string]map[string]interface{}
|
||||||
InterpolaterVarLock *sync.Mutex
|
InterpolaterVarLock *sync.Mutex
|
||||||
|
|
||||||
Hooks []Hook
|
Hooks []Hook
|
||||||
|
@ -311,7 +311,7 @@ func (ctx *BuiltinEvalContext) Path() []string {
|
||||||
return ctx.PathValue
|
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()
|
ctx.InterpolaterVarLock.Lock()
|
||||||
defer ctx.InterpolaterVarLock.Unlock()
|
defer ctx.InterpolaterVarLock.Unlock()
|
||||||
|
|
||||||
|
@ -322,7 +322,7 @@ func (ctx *BuiltinEvalContext) SetVariables(n string, vs map[string]string) {
|
||||||
|
|
||||||
vars := ctx.InterpolaterVars[key]
|
vars := ctx.InterpolaterVars[key]
|
||||||
if vars == nil {
|
if vars == nil {
|
||||||
vars = make(map[string]string)
|
vars = make(map[string]interface{})
|
||||||
ctx.InterpolaterVars[key] = vars
|
ctx.InterpolaterVars[key] = vars
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,7 +74,7 @@ type MockEvalContext struct {
|
||||||
|
|
||||||
SetVariablesCalled bool
|
SetVariablesCalled bool
|
||||||
SetVariablesModule string
|
SetVariablesModule string
|
||||||
SetVariablesVariables map[string]string
|
SetVariablesVariables map[string]interface{}
|
||||||
|
|
||||||
DiffCalled bool
|
DiffCalled bool
|
||||||
DiffDiff *Diff
|
DiffDiff *Diff
|
||||||
|
@ -183,7 +183,7 @@ func (c *MockEvalContext) Path() []string {
|
||||||
return c.PathPath
|
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.SetVariablesCalled = true
|
||||||
c.SetVariablesModule = n
|
c.SetVariablesModule = n
|
||||||
c.SetVariablesVariables = vs
|
c.SetVariablesVariables = vs
|
||||||
|
|
|
@ -2,6 +2,7 @@ package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/config"
|
"github.com/hashicorp/terraform/config"
|
||||||
)
|
)
|
||||||
|
@ -45,7 +46,8 @@ type EvalWriteOutput struct {
|
||||||
func (n *EvalWriteOutput) Eval(ctx EvalContext) (interface{}, error) {
|
func (n *EvalWriteOutput) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
cfg, err := ctx.Interpolate(n.Value, nil)
|
cfg, err := ctx.Interpolate(n.Value, nil)
|
||||||
if err != 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()
|
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
|
switch valueTyped := valueRaw.(type) {
|
||||||
if list, ok := valueRaw.([]interface{}); ok {
|
case string:
|
||||||
valueRaw = list[0]
|
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
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/errwrap"
|
|
||||||
"github.com/hashicorp/terraform/config"
|
"github.com/hashicorp/terraform/config"
|
||||||
"github.com/hashicorp/terraform/config/module"
|
"github.com/hashicorp/terraform/config/module"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
|
@ -26,7 +25,7 @@ import (
|
||||||
// use of the values since it is only valid to pass string values. The
|
// 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.
|
// structure is in place for extension of the type system, however.
|
||||||
type EvalTypeCheckVariable struct {
|
type EvalTypeCheckVariable struct {
|
||||||
Variables map[string]string
|
Variables map[string]interface{}
|
||||||
ModulePath []string
|
ModulePath []string
|
||||||
ModuleTree *module.Tree
|
ModuleTree *module.Tree
|
||||||
}
|
}
|
||||||
|
@ -43,12 +42,18 @@ func (n *EvalTypeCheckVariable) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
prototypes[variable.Name] = variable.Type()
|
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 {
|
for name, declaredType := range prototypes {
|
||||||
// This is only necessary when we _actually_ check. It is left as a reminder
|
// 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
|
// 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
|
// of strings and maps - where the only valid inter-module variable type is
|
||||||
// string.
|
// string.
|
||||||
_, ok := n.Variables[name]
|
proposedValue, ok := n.Variables[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
// This means the default value should be used as no overriding value
|
// This means the default value should be used as no overriding value
|
||||||
// has been set. Therefore we should continue as no check is necessary.
|
// 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:
|
case config.VariableTypeString:
|
||||||
// This will need actual verification once we aren't dealing with
|
// This will need actual verification once we aren't dealing with
|
||||||
// a map[string]string but this is sufficient for now.
|
// a map[string]string but this is sufficient for now.
|
||||||
|
switch proposedValue.(type) {
|
||||||
|
case string:
|
||||||
continue
|
continue
|
||||||
default:
|
default:
|
||||||
// Only display a module if we are not in the root module
|
return nil, fmt.Errorf("variable %s%s should be type %s, got %T",
|
||||||
modulePathDescription := fmt.Sprintf(" in module %s", strings.Join(n.ModulePath[1:], "."))
|
name, modulePathDescription, declaredType.Printable(), proposedValue)
|
||||||
if len(n.ModulePath) == 1 {
|
|
||||||
modulePathDescription = ""
|
|
||||||
}
|
}
|
||||||
|
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
|
// This will need the actual type substituting when we have more than
|
||||||
// just strings and maps.
|
// just strings and maps.
|
||||||
return nil, fmt.Errorf("variable %s%s should be type %s, got type string",
|
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.
|
// explicitly for interpolation later.
|
||||||
type EvalSetVariables struct {
|
type EvalSetVariables struct {
|
||||||
Module *string
|
Module *string
|
||||||
Variables map[string]string
|
Variables map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: test
|
// TODO: test
|
||||||
|
@ -94,30 +109,42 @@ func (n *EvalSetVariables) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
// mapping.
|
// mapping.
|
||||||
type EvalVariableBlock struct {
|
type EvalVariableBlock struct {
|
||||||
Config **ResourceConfig
|
Config **ResourceConfig
|
||||||
Variables map[string]string
|
VariableValues map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: test
|
// TODO: test
|
||||||
func (n *EvalVariableBlock) Eval(ctx EvalContext) (interface{}, error) {
|
func (n *EvalVariableBlock) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
// Clear out the existing mapping
|
// Clear out the existing mapping
|
||||||
for k, _ := range n.Variables {
|
for k, _ := range n.VariableValues {
|
||||||
delete(n.Variables, k)
|
delete(n.VariableValues, k)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get our configuration
|
// Get our configuration
|
||||||
rc := *n.Config
|
rc := *n.Config
|
||||||
for k, v := range rc.Config {
|
for k, v := range rc.Config {
|
||||||
var vStr string
|
var vString string
|
||||||
if err := mapstructure.WeakDecode(v, &vStr); err != nil {
|
if err := mapstructure.WeakDecode(v, &vString); err == nil {
|
||||||
return nil, errwrap.Wrapf(fmt.Sprintf(
|
n.VariableValues[k] = vString
|
||||||
"%s: error reading value: {{err}}", k), err)
|
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 {
|
for k, _ := range rc.Raw {
|
||||||
if _, ok := n.Variables[k]; !ok {
|
if _, ok := n.VariableValues[k]; !ok {
|
||||||
n.Variables[k] = config.UnknownVariableValue
|
n.VariableValues[k] = config.UnknownVariableValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,7 @@ func (n *GraphNodeConfigModule) Expand(b GraphBuilder) (GraphNodeSubgraph, error
|
||||||
return &graphNodeModuleExpanded{
|
return &graphNodeModuleExpanded{
|
||||||
Original: n,
|
Original: n,
|
||||||
Graph: graph,
|
Graph: graph,
|
||||||
Variables: make(map[string]string),
|
Variables: make(map[string]interface{}),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +107,7 @@ type graphNodeModuleExpanded struct {
|
||||||
// Variables is a map of the input variables. This reference should
|
// Variables is a map of the input variables. This reference should
|
||||||
// be shared with ModuleInputTransformer in order to create a connection
|
// be shared with ModuleInputTransformer in order to create a connection
|
||||||
// where the variables are set properly.
|
// where the variables are set properly.
|
||||||
Variables map[string]string
|
Variables map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *graphNodeModuleExpanded) Name() string {
|
func (n *graphNodeModuleExpanded) Name() string {
|
||||||
|
@ -148,7 +148,7 @@ func (n *graphNodeModuleExpanded) EvalTree() EvalNode {
|
||||||
|
|
||||||
&EvalVariableBlock{
|
&EvalVariableBlock{
|
||||||
Config: &resourceConfig,
|
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
|
// Otherwise, interpolate the value of this variable and set it
|
||||||
// within the variables mapping.
|
// within the variables mapping.
|
||||||
var config *ResourceConfig
|
var config *ResourceConfig
|
||||||
variables := make(map[string]string)
|
variables := make(map[string]interface{})
|
||||||
return &EvalSequence{
|
return &EvalSequence{
|
||||||
Nodes: []EvalNode{
|
Nodes: []EvalNode{
|
||||||
&EvalInterpolate{
|
&EvalInterpolate{
|
||||||
|
@ -124,7 +124,7 @@ func (n *GraphNodeConfigVariable) EvalTree() EvalNode {
|
||||||
|
|
||||||
&EvalVariableBlock{
|
&EvalVariableBlock{
|
||||||
Config: &config,
|
Config: &config,
|
||||||
Variables: variables,
|
VariableValues: variables,
|
||||||
},
|
},
|
||||||
|
|
||||||
&EvalTypeCheckVariable{
|
&EvalTypeCheckVariable{
|
||||||
|
|
|
@ -27,7 +27,7 @@ type ContextGraphWalker struct {
|
||||||
once sync.Once
|
once sync.Once
|
||||||
contexts map[string]*BuiltinEvalContext
|
contexts map[string]*BuiltinEvalContext
|
||||||
contextLock sync.Mutex
|
contextLock sync.Mutex
|
||||||
interpolaterVars map[string]map[string]string
|
interpolaterVars map[string]map[string]interface{}
|
||||||
interpolaterVarLock sync.Mutex
|
interpolaterVarLock sync.Mutex
|
||||||
providerCache map[string]ResourceProvider
|
providerCache map[string]ResourceProvider
|
||||||
providerConfigCache map[string]*ResourceConfig
|
providerConfigCache map[string]*ResourceConfig
|
||||||
|
@ -49,7 +49,7 @@ func (w *ContextGraphWalker) EnterPath(path []string) EvalContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup the variables for this interpolater
|
// Setup the variables for this interpolater
|
||||||
variables := make(map[string]string)
|
variables := make(map[string]interface{})
|
||||||
if len(path) <= 1 {
|
if len(path) <= 1 {
|
||||||
for k, v := range w.Context.variables {
|
for k, v := range w.Context.variables {
|
||||||
variables[k] = v
|
variables[k] = v
|
||||||
|
@ -85,8 +85,8 @@ func (w *ContextGraphWalker) EnterPath(path []string) EvalContext {
|
||||||
Module: w.Context.module,
|
Module: w.Context.module,
|
||||||
State: w.Context.state,
|
State: w.Context.state,
|
||||||
StateLock: &w.Context.stateLock,
|
StateLock: &w.Context.stateLock,
|
||||||
Variables: variables,
|
VariableValues: variables,
|
||||||
VariablesLock: &w.interpolaterVarLock,
|
VariableValuesLock: &w.interpolaterVarLock,
|
||||||
},
|
},
|
||||||
InterpolaterVars: w.interpolaterVars,
|
InterpolaterVars: w.interpolaterVars,
|
||||||
InterpolaterVarLock: &w.interpolaterVarLock,
|
InterpolaterVarLock: &w.interpolaterVarLock,
|
||||||
|
@ -150,5 +150,5 @@ func (w *ContextGraphWalker) init() {
|
||||||
w.providerCache = make(map[string]ResourceProvider, 5)
|
w.providerCache = make(map[string]ResourceProvider, 5)
|
||||||
w.providerConfigCache = make(map[string]*ResourceConfig, 5)
|
w.providerConfigCache = make(map[string]*ResourceConfig, 5)
|
||||||
w.provisionerCache = make(map[string]ResourceProvisioner, 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"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hil"
|
||||||
"github.com/hashicorp/hil/ast"
|
"github.com/hashicorp/hil/ast"
|
||||||
"github.com/hashicorp/terraform/config"
|
"github.com/hashicorp/terraform/config"
|
||||||
"github.com/hashicorp/terraform/config/module"
|
"github.com/hashicorp/terraform/config/module"
|
||||||
|
@ -27,8 +28,8 @@ type Interpolater struct {
|
||||||
Module *module.Tree
|
Module *module.Tree
|
||||||
State *State
|
State *State
|
||||||
StateLock *sync.RWMutex
|
StateLock *sync.RWMutex
|
||||||
Variables map[string]string
|
VariableValues map[string]interface{}
|
||||||
VariablesLock *sync.Mutex
|
VariableValuesLock *sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// InterpolationScope is the current scope of execution. This is required
|
// 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:])
|
mod = i.Module.Child(scope.Path[1:])
|
||||||
}
|
}
|
||||||
for _, v := range mod.Config().Variables {
|
for _, v := range mod.Config().Variables {
|
||||||
for k, val := range v.DefaultsMap() {
|
// Set default variables
|
||||||
result[k] = ast.Variable{
|
if v.Default == nil {
|
||||||
Value: val,
|
continue
|
||||||
Type: ast.TypeString,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
func unknownVariable() ast.Variable {
|
||||||
return ast.Variable{
|
return ast.Variable{
|
||||||
Type: ast.TypeString,
|
Type: ast.TypeString,
|
||||||
|
@ -167,7 +162,11 @@ func (i *Interpolater) valueModuleVar(
|
||||||
} else {
|
} else {
|
||||||
// Get the value from the outputs
|
// Get the value from the outputs
|
||||||
if value, ok := mod.Outputs[v.Field]; ok {
|
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 {
|
} else {
|
||||||
// Same reasons as the comment above.
|
// Same reasons as the comment above.
|
||||||
result[n] = unknownVariable()
|
result[n] = unknownVariable()
|
||||||
|
@ -289,33 +288,44 @@ func (i *Interpolater) valueUserVar(
|
||||||
n string,
|
n string,
|
||||||
v *config.UserVariable,
|
v *config.UserVariable,
|
||||||
result map[string]ast.Variable) error {
|
result map[string]ast.Variable) error {
|
||||||
i.VariablesLock.Lock()
|
i.VariableValuesLock.Lock()
|
||||||
defer i.VariablesLock.Unlock()
|
defer i.VariableValuesLock.Unlock()
|
||||||
val, ok := i.Variables[v.Name]
|
val, ok := i.VariableValues[v.Name]
|
||||||
if ok {
|
if ok {
|
||||||
result[n] = ast.Variable{
|
varValue, err := hil.InterfaceToVariable(val)
|
||||||
Value: val,
|
if err != nil {
|
||||||
Type: ast.TypeString,
|
return fmt.Errorf("cannot convert %s value %q to an ast.Variable for interpolation: %s",
|
||||||
|
v.Name, val, err)
|
||||||
}
|
}
|
||||||
|
result[n] = varValue
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := result[n]; !ok && i.Operation == walkValidate {
|
if _, ok := result[n]; !ok && i.Operation == walkValidate {
|
||||||
result[n] = ast.Variable{
|
result[n] = unknownVariable()
|
||||||
Value: config.UnknownVariableValue,
|
|
||||||
Type: ast.TypeString,
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look up if we have any variables with this prefix because
|
// Look up if we have any variables with this prefix because
|
||||||
// those are map overrides. Include those.
|
// those are map overrides. Include those.
|
||||||
for k, val := range i.Variables {
|
for k, val := range i.VariableValues {
|
||||||
if strings.HasPrefix(k, v.Name+".") {
|
if strings.HasPrefix(k, v.Name+".") {
|
||||||
result["var."+k] = ast.Variable{
|
keyComponents := strings.Split(k, ".")
|
||||||
Value: val,
|
overrideKey := keyComponents[len(keyComponents)-1]
|
||||||
Type: ast.TypeString,
|
|
||||||
|
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
|
return false
|
||||||
}
|
}
|
||||||
for k, v := range m.Outputs {
|
for k, v := range m.Outputs {
|
||||||
if other.Outputs[k] != v {
|
if !reflect.DeepEqual(other.Outputs[k], v) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -803,7 +803,27 @@ func (m *ModuleState) String() string {
|
||||||
|
|
||||||
for _, k := range ks {
|
for _, k := range ks {
|
||||||
v := m.Outputs[k]
|
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:
|
Outputs:
|
||||||
|
|
||||||
foo_num = bar,bar,bar
|
foo_num = [bar,bar,bar]
|
||||||
`
|
`
|
||||||
|
|
||||||
const testTerraformApplyOutputMultiStr = `
|
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" {
|
resource "aws_instance" "bar" {
|
||||||
foo = "${var.foo}"
|
foo = "${var.foo}"
|
||||||
bar = "${lookup(var.amis, 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" {
|
variable "amis" {
|
||||||
|
type = "map"
|
||||||
default = {
|
default = {
|
||||||
us-east-1 = "ami-b8b061d0"
|
us-east-1 = "ami-b8b061d0"
|
||||||
us-west-2 = "ami-ef5e24df"
|
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
|
A variable becomes a mapping when it has a type of "map" assigned, or has a
|
||||||
map like above. There is no way to create a required map.
|
default value that is a map like above.
|
||||||
|
|
||||||
Then, replace the "aws\_instance" with the following:
|
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
|
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
|
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>
|
<a id="assigning-mappings"></a>
|
||||||
## Assigning Mappings
|
## Assigning Mappings
|
||||||
|
|
Loading…
Reference in New Issue