From b772f8078dd7ea3599782d2591f0879026a8ad11 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 21 Jul 2014 08:55:45 -0700 Subject: [PATCH] config: detect UserMapVariable --- config/config.go | 87 -------------------------- config/config_test.go | 86 -------------------------- config/variable.go | 132 +++++++++++++++++++++++++++++++++++++++- config/variable_test.go | 122 +++++++++++++++++++++++++++++++++++++ 4 files changed, 251 insertions(+), 176 deletions(-) diff --git a/config/config.go b/config/config.go index 656f33a62..be4bf00f5 100644 --- a/config/config.go +++ b/config/config.go @@ -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 -} diff --git a/config/config_test.go b/config/config_test.go index f4a2ce0d7..000719ee5 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -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"}, diff --git a/config/variable.go b/config/variable.go index 40a3b28df..96b49f9e6 100644 --- a/config/variable.go +++ b/config/variable.go @@ -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 { diff --git a/config/variable_test.go b/config/variable_test.go index 7e3b5fc11..1dacc4f41 100644 --- a/config/variable_test.go +++ b/config/variable_test.go @@ -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{