config: detect UserMapVariable
This commit is contained in:
parent
b8a0a02217
commit
b772f8078d
|
@ -4,7 +4,6 @@ package config
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/multierror"
|
||||
|
@ -64,38 +63,6 @@ type Output struct {
|
|||
RawConfig *RawConfig
|
||||
}
|
||||
|
||||
// An InterpolatedVariable is a variable that is embedded within a string
|
||||
// in the configuration, such as "hello ${world}" (world in this case is
|
||||
// an interpolated variable).
|
||||
//
|
||||
// These variables can come from a variety of sources, represented by
|
||||
// implementations of this interface.
|
||||
type InterpolatedVariable interface {
|
||||
FullKey() string
|
||||
}
|
||||
|
||||
// A ResourceVariable is a variable that is referencing the field
|
||||
// of a resource, such as "${aws_instance.foo.ami}"
|
||||
type ResourceVariable struct {
|
||||
Type string // Resource type, i.e. "aws_instance"
|
||||
Name string // Resource name
|
||||
Field string // Resource field
|
||||
|
||||
Multi bool // True if multi-variable: aws_instance.foo.*.id
|
||||
Index int // Index for multi-variable: aws_instance.foo.1.id == 1
|
||||
|
||||
key string
|
||||
}
|
||||
|
||||
// A UserVariable is a variable that is referencing a user variable
|
||||
// that is inputted from outside the configuration. This looks like
|
||||
// "${var.foo}"
|
||||
type UserVariable struct {
|
||||
Name string
|
||||
|
||||
key string
|
||||
}
|
||||
|
||||
// ProviderConfigName returns the name of the provider configuration in
|
||||
// the given mapping that maps to the proper provider configuration
|
||||
// for this resource.
|
||||
|
@ -334,57 +301,3 @@ func (v *Variable) mergerMerge(m merger) merger {
|
|||
func (v *Variable) Required() bool {
|
||||
return v.Default == nil
|
||||
}
|
||||
|
||||
func NewResourceVariable(key string) (*ResourceVariable, error) {
|
||||
parts := strings.SplitN(key, ".", 3)
|
||||
field := parts[2]
|
||||
multi := false
|
||||
var index int
|
||||
|
||||
if idx := strings.Index(field, "."); idx != -1 {
|
||||
indexStr := field[:idx]
|
||||
multi = indexStr == "*"
|
||||
index = -1
|
||||
|
||||
if !multi {
|
||||
indexInt, err := strconv.ParseInt(indexStr, 0, 0)
|
||||
if err == nil {
|
||||
multi = true
|
||||
index = int(indexInt)
|
||||
}
|
||||
}
|
||||
|
||||
if multi {
|
||||
field = field[idx+1:]
|
||||
}
|
||||
}
|
||||
|
||||
return &ResourceVariable{
|
||||
Type: parts[0],
|
||||
Name: parts[1],
|
||||
Field: field,
|
||||
Multi: multi,
|
||||
Index: index,
|
||||
key: key,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (v *ResourceVariable) ResourceId() string {
|
||||
return fmt.Sprintf("%s.%s", v.Type, v.Name)
|
||||
}
|
||||
|
||||
func (v *ResourceVariable) FullKey() string {
|
||||
return v.key
|
||||
}
|
||||
|
||||
func NewUserVariable(key string) (*UserVariable, error) {
|
||||
name := key[len("var."):]
|
||||
return &UserVariable{
|
||||
key: key,
|
||||
Name: name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (v *UserVariable) FullKey() string {
|
||||
return v.key
|
||||
}
|
||||
|
|
|
@ -82,92 +82,6 @@ func TestConfigValidate_varDefaultBadType(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNewResourceVariable(t *testing.T) {
|
||||
v, err := NewResourceVariable("foo.bar.baz")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if v.Type != "foo" {
|
||||
t.Fatalf("bad: %#v", v)
|
||||
}
|
||||
if v.Name != "bar" {
|
||||
t.Fatalf("bad: %#v", v)
|
||||
}
|
||||
if v.Field != "baz" {
|
||||
t.Fatalf("bad: %#v", v)
|
||||
}
|
||||
if v.Multi {
|
||||
t.Fatal("should not be multi")
|
||||
}
|
||||
|
||||
if v.FullKey() != "foo.bar.baz" {
|
||||
t.Fatalf("bad: %#v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceVariable_Multi(t *testing.T) {
|
||||
v, err := NewResourceVariable("foo.bar.*.baz")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if v.Type != "foo" {
|
||||
t.Fatalf("bad: %#v", v)
|
||||
}
|
||||
if v.Name != "bar" {
|
||||
t.Fatalf("bad: %#v", v)
|
||||
}
|
||||
if v.Field != "baz" {
|
||||
t.Fatalf("bad: %#v", v)
|
||||
}
|
||||
if !v.Multi {
|
||||
t.Fatal("should be multi")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceVariable_MultiIndex(t *testing.T) {
|
||||
cases := []struct {
|
||||
Input string
|
||||
Index int
|
||||
Field string
|
||||
}{
|
||||
{"foo.bar.*.baz", -1, "baz"},
|
||||
{"foo.bar.0.baz", 0, "baz"},
|
||||
{"foo.bar.5.baz", 5, "baz"},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
v, err := NewResourceVariable(tc.Input)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if !v.Multi {
|
||||
t.Fatalf("should be multi: %s", tc.Input)
|
||||
}
|
||||
if v.Index != tc.Index {
|
||||
t.Fatalf("bad: %d\n\n%s", v.Index, tc.Input)
|
||||
}
|
||||
if v.Field != tc.Field {
|
||||
t.Fatalf("bad: %s\n\n%s", v.Field, tc.Input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewUserVariable(t *testing.T) {
|
||||
v, err := NewUserVariable("var.bar")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if v.Name != "bar" {
|
||||
t.Fatalf("bad: %#v", v.Name)
|
||||
}
|
||||
if v.FullKey() != "var.bar" {
|
||||
t.Fatalf("bad: %#v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderConfigName(t *testing.T) {
|
||||
pcs := []*ProviderConfig{
|
||||
&ProviderConfig{Name: "aw"},
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/reflectwalk"
|
||||
|
@ -16,6 +17,126 @@ func init() {
|
|||
varRegexp = regexp.MustCompile(`(?i)(\$+)\{([*-.a-z0-9_]+)\}`)
|
||||
}
|
||||
|
||||
// An InterpolatedVariable is a variable that is embedded within a string
|
||||
// in the configuration, such as "hello ${world}" (world in this case is
|
||||
// an interpolated variable).
|
||||
//
|
||||
// These variables can come from a variety of sources, represented by
|
||||
// implementations of this interface.
|
||||
type InterpolatedVariable interface {
|
||||
FullKey() string
|
||||
}
|
||||
|
||||
// A ResourceVariable is a variable that is referencing the field
|
||||
// of a resource, such as "${aws_instance.foo.ami}"
|
||||
type ResourceVariable struct {
|
||||
Type string // Resource type, i.e. "aws_instance"
|
||||
Name string // Resource name
|
||||
Field string // Resource field
|
||||
|
||||
Multi bool // True if multi-variable: aws_instance.foo.*.id
|
||||
Index int // Index for multi-variable: aws_instance.foo.1.id == 1
|
||||
|
||||
key string
|
||||
}
|
||||
|
||||
func (v *ResourceVariable) ResourceId() string {
|
||||
return fmt.Sprintf("%s.%s", v.Type, v.Name)
|
||||
}
|
||||
|
||||
func (v *ResourceVariable) FullKey() string {
|
||||
return v.key
|
||||
}
|
||||
|
||||
// A UserVariable is a variable that is referencing a user variable
|
||||
// that is inputted from outside the configuration. This looks like
|
||||
// "${var.foo}"
|
||||
type UserVariable struct {
|
||||
Name string
|
||||
|
||||
key string
|
||||
}
|
||||
|
||||
func (v *UserVariable) FullKey() string {
|
||||
return v.key
|
||||
}
|
||||
|
||||
// A UserMapVariable is a variable that is referencing a user
|
||||
// variable that is a map. This looks like "${var.amis.us-east-1}"
|
||||
type UserMapVariable struct {
|
||||
Name string
|
||||
Elem string
|
||||
|
||||
key string
|
||||
}
|
||||
|
||||
func NewUserMapVariable(key string) (*UserMapVariable, error) {
|
||||
name := key[len("var."):]
|
||||
idx := strings.Index(name, ".")
|
||||
if idx == -1 {
|
||||
return nil, fmt.Errorf("not a user map variable: %s", key)
|
||||
}
|
||||
|
||||
elem := name[idx+1:]
|
||||
name = name[:idx]
|
||||
return &UserMapVariable{
|
||||
Name: name,
|
||||
Elem: elem,
|
||||
|
||||
key: key,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (v *UserMapVariable) FullKey() string {
|
||||
return v.key
|
||||
}
|
||||
|
||||
func (v *UserMapVariable) GoString() string {
|
||||
return fmt.Sprintf("%#v", *v)
|
||||
}
|
||||
|
||||
func NewResourceVariable(key string) (*ResourceVariable, error) {
|
||||
parts := strings.SplitN(key, ".", 3)
|
||||
field := parts[2]
|
||||
multi := false
|
||||
var index int
|
||||
|
||||
if idx := strings.Index(field, "."); idx != -1 {
|
||||
indexStr := field[:idx]
|
||||
multi = indexStr == "*"
|
||||
index = -1
|
||||
|
||||
if !multi {
|
||||
indexInt, err := strconv.ParseInt(indexStr, 0, 0)
|
||||
if err == nil {
|
||||
multi = true
|
||||
index = int(indexInt)
|
||||
}
|
||||
}
|
||||
|
||||
if multi {
|
||||
field = field[idx+1:]
|
||||
}
|
||||
}
|
||||
|
||||
return &ResourceVariable{
|
||||
Type: parts[0],
|
||||
Name: parts[1],
|
||||
Field: field,
|
||||
Multi: multi,
|
||||
Index: index,
|
||||
key: key,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewUserVariable(key string) (*UserVariable, error) {
|
||||
name := key[len("var."):]
|
||||
return &UserVariable{
|
||||
key: key,
|
||||
Name: name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ReplaceVariables takes a configuration and a mapping of variables
|
||||
// and performs the structure walking necessary to properly replace
|
||||
// all the variables.
|
||||
|
@ -80,10 +201,15 @@ func (w *variableDetectWalker) Primitive(v reflect.Value) error {
|
|||
key, key)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(key, "var.") {
|
||||
iv, err = NewUserVariable(key)
|
||||
} else {
|
||||
if !strings.HasPrefix(key, "var.") {
|
||||
iv, err = NewResourceVariable(key)
|
||||
} else {
|
||||
varKey := key[len("var."):]
|
||||
if strings.Index(varKey, ".") == -1 {
|
||||
iv, err = NewUserVariable(key)
|
||||
} else {
|
||||
iv, err = NewUserMapVariable(key)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
|
|
@ -36,6 +36,44 @@ func BenchmarkVariableReplaceWalker(b *testing.B) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNewResourceVariable(t *testing.T) {
|
||||
v, err := NewResourceVariable("foo.bar.baz")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if v.Type != "foo" {
|
||||
t.Fatalf("bad: %#v", v)
|
||||
}
|
||||
if v.Name != "bar" {
|
||||
t.Fatalf("bad: %#v", v)
|
||||
}
|
||||
if v.Field != "baz" {
|
||||
t.Fatalf("bad: %#v", v)
|
||||
}
|
||||
if v.Multi {
|
||||
t.Fatal("should not be multi")
|
||||
}
|
||||
|
||||
if v.FullKey() != "foo.bar.baz" {
|
||||
t.Fatalf("bad: %#v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewUserVariable(t *testing.T) {
|
||||
v, err := NewUserVariable("var.bar")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if v.Name != "bar" {
|
||||
t.Fatalf("bad: %#v", v.Name)
|
||||
}
|
||||
if v.FullKey() != "var.bar" {
|
||||
t.Fatalf("bad: %#v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplaceVariables(t *testing.T) {
|
||||
input := "foo-${var.bar}"
|
||||
expected := "foo-bar"
|
||||
|
@ -55,6 +93,66 @@ func TestReplaceVariables(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestResourceVariable_impl(t *testing.T) {
|
||||
var _ InterpolatedVariable = new(ResourceVariable)
|
||||
}
|
||||
|
||||
func TestResourceVariable_Multi(t *testing.T) {
|
||||
v, err := NewResourceVariable("foo.bar.*.baz")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if v.Type != "foo" {
|
||||
t.Fatalf("bad: %#v", v)
|
||||
}
|
||||
if v.Name != "bar" {
|
||||
t.Fatalf("bad: %#v", v)
|
||||
}
|
||||
if v.Field != "baz" {
|
||||
t.Fatalf("bad: %#v", v)
|
||||
}
|
||||
if !v.Multi {
|
||||
t.Fatal("should be multi")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceVariable_MultiIndex(t *testing.T) {
|
||||
cases := []struct {
|
||||
Input string
|
||||
Index int
|
||||
Field string
|
||||
}{
|
||||
{"foo.bar.*.baz", -1, "baz"},
|
||||
{"foo.bar.0.baz", 0, "baz"},
|
||||
{"foo.bar.5.baz", 5, "baz"},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
v, err := NewResourceVariable(tc.Input)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if !v.Multi {
|
||||
t.Fatalf("should be multi: %s", tc.Input)
|
||||
}
|
||||
if v.Index != tc.Index {
|
||||
t.Fatalf("bad: %d\n\n%s", v.Index, tc.Input)
|
||||
}
|
||||
if v.Field != tc.Field {
|
||||
t.Fatalf("bad: %s\n\n%s", v.Field, tc.Input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserVariable_impl(t *testing.T) {
|
||||
var _ InterpolatedVariable = new(UserVariable)
|
||||
}
|
||||
|
||||
func TestUserMapVariable_impl(t *testing.T) {
|
||||
var _ InterpolatedVariable = new(UserMapVariable)
|
||||
}
|
||||
|
||||
func TestVariableDetectWalker(t *testing.T) {
|
||||
w := new(variableDetectWalker)
|
||||
|
||||
|
@ -138,6 +236,30 @@ func TestVariableDetectWalker_empty(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestVariableDetectWalker_userMap(t *testing.T) {
|
||||
w := new(variableDetectWalker)
|
||||
|
||||
str := `foo ${var.foo.bar}`
|
||||
if err := w.Primitive(reflect.ValueOf(str)); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(w.Variables) != 1 {
|
||||
t.Fatalf("bad: %#v", w.Variables)
|
||||
}
|
||||
|
||||
v := w.Variables["var.foo.bar"].(*UserMapVariable)
|
||||
if v.FullKey() != "var.foo.bar" {
|
||||
t.Fatalf("bad: %#v", w.Variables)
|
||||
}
|
||||
if v.Name != "foo" {
|
||||
t.Fatalf("bad: %#v", w.Variables)
|
||||
}
|
||||
if v.Elem != "bar" {
|
||||
t.Fatalf("bad: %#v", w.Variables)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVariableReplaceWalker(t *testing.T) {
|
||||
w := &variableReplaceWalker{
|
||||
Values: map[string]string{
|
||||
|
|
Loading…
Reference in New Issue