commit
e40bd46ffd
158
config/config.go
158
config/config.go
|
@ -4,10 +4,11 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/flatmap"
|
||||||
"github.com/hashicorp/terraform/helper/multierror"
|
"github.com/hashicorp/terraform/helper/multierror"
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config is the configuration that comes from loading a collection
|
// Config is the configuration that comes from loading a collection
|
||||||
|
@ -53,9 +54,8 @@ type Provisioner struct {
|
||||||
// Variable is a variable defined within the configuration.
|
// Variable is a variable defined within the configuration.
|
||||||
type Variable struct {
|
type Variable struct {
|
||||||
Name string
|
Name string
|
||||||
Default string
|
Default interface{}
|
||||||
Description string
|
Description string
|
||||||
defaultSet bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output is an output defined within the configuration. An output is
|
// Output is an output defined within the configuration. An output is
|
||||||
|
@ -65,37 +65,15 @@ type Output struct {
|
||||||
RawConfig *RawConfig
|
RawConfig *RawConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// An InterpolatedVariable is a variable that is embedded within a string
|
// VariableType is the type of value a variable is holding, and returned
|
||||||
// in the configuration, such as "hello ${world}" (world in this case is
|
// by the Type() function on variables.
|
||||||
// an interpolated variable).
|
type VariableType byte
|
||||||
//
|
|
||||||
// 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
|
const (
|
||||||
// of a resource, such as "${aws_instance.foo.ami}"
|
VariableTypeUnknown VariableType = iota
|
||||||
type ResourceVariable struct {
|
VariableTypeString
|
||||||
Type string // Resource type, i.e. "aws_instance"
|
VariableTypeMap
|
||||||
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
|
// ProviderConfigName returns the name of the provider configuration in
|
||||||
// the given mapping that maps to the proper provider configuration
|
// the given mapping that maps to the proper provider configuration
|
||||||
|
@ -132,6 +110,14 @@ func (c *Config) Validate() error {
|
||||||
varMap[v.Name] = v
|
varMap[v.Name] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, v := range c.Variables {
|
||||||
|
if v.Type() == VariableTypeUnknown {
|
||||||
|
errs = append(errs, fmt.Errorf(
|
||||||
|
"Variable '%s': must be string or mapping",
|
||||||
|
v.Name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check for references to user variables that do not actually
|
// Check for references to user variables that do not actually
|
||||||
// exist and record those errors.
|
// exist and record those errors.
|
||||||
for source, vs := range vars {
|
for source, vs := range vars {
|
||||||
|
@ -227,8 +213,8 @@ func (c *Config) Validate() error {
|
||||||
// are valid in the Validate step.
|
// are valid in the Validate step.
|
||||||
func (c *Config) allVariables() map[string][]InterpolatedVariable {
|
func (c *Config) allVariables() map[string][]InterpolatedVariable {
|
||||||
result := make(map[string][]InterpolatedVariable)
|
result := make(map[string][]InterpolatedVariable)
|
||||||
for n, pc := range c.ProviderConfigs {
|
for _, pc := range c.ProviderConfigs {
|
||||||
source := fmt.Sprintf("provider config '%s'", n)
|
source := fmt.Sprintf("provider config '%s'", pc.Name)
|
||||||
for _, v := range pc.RawConfig.Variables {
|
for _, v := range pc.RawConfig.Variables {
|
||||||
result[source] = append(result[source], v)
|
result[source] = append(result[source], v)
|
||||||
}
|
}
|
||||||
|
@ -302,6 +288,28 @@ 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
|
||||||
|
@ -310,9 +318,8 @@ func (v *Variable) Merge(v2 *Variable) *Variable {
|
||||||
// The names should be the same, but the second name always wins.
|
// The names should be the same, but the second name always wins.
|
||||||
result.Name = v2.Name
|
result.Name = v2.Name
|
||||||
|
|
||||||
if v2.defaultSet {
|
if v2.Default != nil {
|
||||||
result.Default = v2.Default
|
result.Default = v2.Default
|
||||||
result.defaultSet = true
|
|
||||||
}
|
}
|
||||||
if v2.Description != "" {
|
if v2.Description != "" {
|
||||||
result.Description = v2.Description
|
result.Description = v2.Description
|
||||||
|
@ -321,6 +328,27 @@ func (v *Variable) Merge(v2 *Variable) *Variable {
|
||||||
return &result
|
return &result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Type returns the type of varialbe this is.
|
||||||
|
func (v *Variable) Type() VariableType {
|
||||||
|
if v.Default == nil {
|
||||||
|
return VariableTypeString
|
||||||
|
}
|
||||||
|
|
||||||
|
var strVal string
|
||||||
|
if err := mapstructure.WeakDecode(v.Default, &strVal); err == nil {
|
||||||
|
v.Default = strVal
|
||||||
|
return VariableTypeString
|
||||||
|
}
|
||||||
|
|
||||||
|
var m map[string]string
|
||||||
|
if err := mapstructure.WeakDecode(v.Default, &m); err == nil {
|
||||||
|
v.Default = m
|
||||||
|
return VariableTypeMap
|
||||||
|
}
|
||||||
|
|
||||||
|
return VariableTypeUnknown
|
||||||
|
}
|
||||||
|
|
||||||
func (v *Variable) mergerName() string {
|
func (v *Variable) mergerName() string {
|
||||||
return v.Name
|
return v.Name
|
||||||
}
|
}
|
||||||
|
@ -331,59 +359,5 @@ func (v *Variable) mergerMerge(m merger) merger {
|
||||||
|
|
||||||
// Required tests whether a variable is required or not.
|
// Required tests whether a variable is required or not.
|
||||||
func (v *Variable) Required() bool {
|
func (v *Variable) Required() bool {
|
||||||
return !v.defaultSet
|
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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -64,89 +65,17 @@ func TestConfigValidate_unknownVar(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewResourceVariable(t *testing.T) {
|
func TestConfigValidate_varDefault(t *testing.T) {
|
||||||
v, err := NewResourceVariable("foo.bar.baz")
|
c := testConfig(t, "validate-var-default")
|
||||||
if err != nil {
|
if err := c.Validate(); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("should be valid: %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) {
|
func TestConfigValidate_varDefaultBadType(t *testing.T) {
|
||||||
v, err := NewResourceVariable("foo.bar.*.baz")
|
c := testConfig(t, "validate-var-default-bad-type")
|
||||||
if err != nil {
|
if err := c.Validate(); err == nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatal("should not be valid")
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,6 +93,43 @@ 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 := Load(filepath.Join(fixtureDir, name, "main.tf"))
|
c, err := Load(filepath.Join(fixtureDir, name, "main.tf"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -0,0 +1,267 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// We really need to replace this with a real parser.
|
||||||
|
var funcRegexp *regexp.Regexp = regexp.MustCompile(
|
||||||
|
`(?i)([a-z0-9_]+)\(\s*(?:([.a-z0-9_]+)\s*,\s*)*([.a-z0-9_]+)\s*\)`)
|
||||||
|
|
||||||
|
// Interpolation is something that can be contained in a "${}" in a
|
||||||
|
// configuration value.
|
||||||
|
//
|
||||||
|
// Interpolations might be simple variable references, or it might be
|
||||||
|
// function calls, or even nested function calls.
|
||||||
|
type Interpolation interface {
|
||||||
|
FullString() string
|
||||||
|
Interpolate(map[string]string) (string, error)
|
||||||
|
Variables() map[string]InterpolatedVariable
|
||||||
|
}
|
||||||
|
|
||||||
|
// InterpolationFunc is the function signature for implementing
|
||||||
|
// callable functions in Terraform configurations.
|
||||||
|
type InterpolationFunc func(map[string]string, ...string) (string, error)
|
||||||
|
|
||||||
|
// An InterpolatedVariable is a variable reference within an interpolation.
|
||||||
|
//
|
||||||
|
// Implementations of this interface represents various sources where
|
||||||
|
// variables can come from: user variables, resources, etc.
|
||||||
|
type InterpolatedVariable interface {
|
||||||
|
FullKey() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// FunctionInterpolation is an Interpolation that executes a function
|
||||||
|
// with some variable number of arguments to generate a value.
|
||||||
|
type FunctionInterpolation struct {
|
||||||
|
Func InterpolationFunc
|
||||||
|
Args []InterpolatedVariable
|
||||||
|
|
||||||
|
key string
|
||||||
|
}
|
||||||
|
|
||||||
|
// VariableInterpolation implements Interpolation for simple variable
|
||||||
|
// interpolation. Ex: "${var.foo}" or "${aws_instance.foo.bar}"
|
||||||
|
type VariableInterpolation struct {
|
||||||
|
Variable InterpolatedVariable
|
||||||
|
|
||||||
|
key 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
|
||||||
|
Elem string
|
||||||
|
|
||||||
|
key string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInterpolation takes some string and returns the valid
|
||||||
|
// Interpolation associated with it, or error if a valid
|
||||||
|
// interpolation could not be found or the interpolation itself
|
||||||
|
// is invalid.
|
||||||
|
func NewInterpolation(v string) (Interpolation, error) {
|
||||||
|
match := funcRegexp.FindStringSubmatch(v)
|
||||||
|
if match != nil {
|
||||||
|
fn, ok := Funcs[match[1]]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"%s: Unknown function '%s'",
|
||||||
|
v, match[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
args := make([]InterpolatedVariable, 0, len(match)-2)
|
||||||
|
for i := 2; i < len(match); i++ {
|
||||||
|
// This can be empty if we have a single argument
|
||||||
|
// due to the format of the regexp.
|
||||||
|
if match[i] == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := NewInterpolatedVariable(match[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
args = append(args, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &FunctionInterpolation{
|
||||||
|
Func: fn,
|
||||||
|
Args: args,
|
||||||
|
|
||||||
|
key: v,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if idx := strings.Index(v, "."); idx >= 0 {
|
||||||
|
v, err := NewInterpolatedVariable(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &VariableInterpolation{
|
||||||
|
Variable: v,
|
||||||
|
key: v.FullKey(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"Interpolation '%s' is not a valid interpolation. " +
|
||||||
|
"Please check your syntax and try again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInterpolatedVariable(v string) (InterpolatedVariable, error) {
|
||||||
|
if !strings.HasPrefix(v, "var.") {
|
||||||
|
return NewResourceVariable(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewUserVariable(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *FunctionInterpolation) FullString() string {
|
||||||
|
return i.key
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *FunctionInterpolation) Interpolate(
|
||||||
|
vs map[string]string) (string, error) {
|
||||||
|
args := make([]string, len(i.Args))
|
||||||
|
for idx, a := range i.Args {
|
||||||
|
k := a.FullKey()
|
||||||
|
v, ok := vs[k]
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf(
|
||||||
|
"%s: variable argument value unknown: %s",
|
||||||
|
i.FullString(),
|
||||||
|
k)
|
||||||
|
}
|
||||||
|
|
||||||
|
args[idx] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return i.Func(vs, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *FunctionInterpolation) Variables() map[string]InterpolatedVariable {
|
||||||
|
result := make(map[string]InterpolatedVariable)
|
||||||
|
for _, a := range i.Args {
|
||||||
|
k := a.FullKey()
|
||||||
|
if _, ok := result[k]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
result[k] = a
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *VariableInterpolation) FullString() string {
|
||||||
|
return i.key
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *VariableInterpolation) Interpolate(
|
||||||
|
vs map[string]string) (string, error) {
|
||||||
|
v, ok := vs[i.key]
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf(
|
||||||
|
"%s: value for variable not found",
|
||||||
|
i.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *VariableInterpolation) Variables() map[string]InterpolatedVariable {
|
||||||
|
return map[string]InterpolatedVariable{i.key: i.Variable}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResourceVariable(key string) (*ResourceVariable, error) {
|
||||||
|
parts := strings.SplitN(key, ".", 3)
|
||||||
|
if len(parts) < 3 {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"%s: resource variables must be three parts: type.name.attr",
|
||||||
|
key)
|
||||||
|
}
|
||||||
|
|
||||||
|
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."):]
|
||||||
|
elem := ""
|
||||||
|
if idx := strings.Index(name, "."); idx > -1 {
|
||||||
|
elem = name[idx+1:]
|
||||||
|
name = name[:idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
return &UserVariable{
|
||||||
|
key: key,
|
||||||
|
|
||||||
|
Name: name,
|
||||||
|
Elem: elem,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *UserVariable) FullKey() string {
|
||||||
|
return v.key
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *UserVariable) GoString() string {
|
||||||
|
return fmt.Sprintf("*%#v", *v)
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Funcs is the mapping of built-in functions for configuration.
|
||||||
|
var Funcs map[string]InterpolationFunc
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Funcs = map[string]InterpolationFunc{
|
||||||
|
"lookup": interpolationFuncLookup,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// interpolationFuncLookup implements the "lookup" function that allows
|
||||||
|
// dynamic lookups of map types within a Terraform configuration.
|
||||||
|
func interpolationFuncLookup(
|
||||||
|
vs map[string]string, args ...string) (string, error) {
|
||||||
|
if len(args) != 2 {
|
||||||
|
return "", fmt.Errorf(
|
||||||
|
"lookup expects 2 arguments, got %d", len(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
k := fmt.Sprintf("var.%s", strings.Join(args, "."))
|
||||||
|
v, ok := vs[k]
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf(
|
||||||
|
"lookup in '%s' failed to find '%s'",
|
||||||
|
args[0], args[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, nil
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInterpolateFuncLookup(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
M map[string]string
|
||||||
|
Args []string
|
||||||
|
Result string
|
||||||
|
Error bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
map[string]string{
|
||||||
|
"var.foo.bar": "baz",
|
||||||
|
},
|
||||||
|
[]string{"foo", "bar"},
|
||||||
|
"baz",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Invalid key
|
||||||
|
{
|
||||||
|
map[string]string{
|
||||||
|
"var.foo.bar": "baz",
|
||||||
|
},
|
||||||
|
[]string{"foo", "baz"},
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Too many args
|
||||||
|
{
|
||||||
|
map[string]string{
|
||||||
|
"var.foo.bar": "baz",
|
||||||
|
},
|
||||||
|
[]string{"foo", "bar", "baz"},
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
actual, err := interpolationFuncLookup(tc.M, tc.Args...)
|
||||||
|
if (err != nil) != tc.Error {
|
||||||
|
t.Fatalf("%d: err: %s", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if actual != tc.Result {
|
||||||
|
t.Fatalf("%d: bad: %#v", i, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,303 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewInterpolation(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Input string
|
||||||
|
Result Interpolation
|
||||||
|
Error bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"foo",
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"var.foo",
|
||||||
|
&VariableInterpolation{
|
||||||
|
Variable: &UserVariable{
|
||||||
|
Name: "foo",
|
||||||
|
key: "var.foo",
|
||||||
|
},
|
||||||
|
key: "var.foo",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"lookup(var.foo, var.bar)",
|
||||||
|
&FunctionInterpolation{
|
||||||
|
Func: nil, // Funcs["lookup"]
|
||||||
|
Args: []InterpolatedVariable{
|
||||||
|
&UserVariable{
|
||||||
|
Name: "foo",
|
||||||
|
key: "var.foo",
|
||||||
|
},
|
||||||
|
&UserVariable{
|
||||||
|
Name: "bar",
|
||||||
|
key: "var.bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
key: "lookup(var.foo, var.bar)",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
actual, err := NewInterpolation(tc.Input)
|
||||||
|
if (err != nil) != tc.Error {
|
||||||
|
t.Fatalf("%d. Error: %s", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is jank, but reflect.DeepEqual never has functions
|
||||||
|
// being the same.
|
||||||
|
if f, ok := actual.(*FunctionInterpolation); ok {
|
||||||
|
f.Func = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(actual, tc.Result) {
|
||||||
|
t.Fatalf("%d bad: %#v", i, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewInterpolatedVariable(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Input string
|
||||||
|
Result InterpolatedVariable
|
||||||
|
Error bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"var.foo",
|
||||||
|
&UserVariable{
|
||||||
|
Name: "foo",
|
||||||
|
key: "var.foo",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
actual, err := NewInterpolatedVariable(tc.Input)
|
||||||
|
if (err != nil) != tc.Error {
|
||||||
|
t.Fatalf("%d. Error: %s", i, err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(actual, tc.Result) {
|
||||||
|
t.Fatalf("%d bad: %#v", i, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 TestNewUserVariable_map(t *testing.T) {
|
||||||
|
v, err := NewUserVariable("var.bar.baz")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Name != "bar" {
|
||||||
|
t.Fatalf("bad: %#v", v.Name)
|
||||||
|
}
|
||||||
|
if v.Elem != "baz" {
|
||||||
|
t.Fatalf("bad: %#v", v.Elem)
|
||||||
|
}
|
||||||
|
if v.FullKey() != "var.bar.baz" {
|
||||||
|
t.Fatalf("bad: %#v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFunctionInterpolation_impl(t *testing.T) {
|
||||||
|
var _ Interpolation = new(FunctionInterpolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFunctionInterpolation(t *testing.T) {
|
||||||
|
v1, err := NewInterpolatedVariable("var.foo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v2, err := NewInterpolatedVariable("var.bar")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn := func(vs map[string]string, args ...string) (string, error) {
|
||||||
|
return strings.Join(args, " "), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
i := &FunctionInterpolation{
|
||||||
|
Func: fn,
|
||||||
|
Args: []InterpolatedVariable{v1, v2},
|
||||||
|
key: "foo",
|
||||||
|
}
|
||||||
|
if i.FullString() != "foo" {
|
||||||
|
t.Fatalf("err: %#v", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := map[string]InterpolatedVariable{
|
||||||
|
"var.foo": v1,
|
||||||
|
"var.bar": v2,
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(i.Variables(), expected) {
|
||||||
|
t.Fatalf("bad: %#v", i.Variables())
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := i.Interpolate(map[string]string{
|
||||||
|
"var.foo": "bar",
|
||||||
|
"var.bar": "baz",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if actual != "bar baz" {
|
||||||
|
t.Fatalf("bad: %#v", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 TestVariableInterpolation_impl(t *testing.T) {
|
||||||
|
var _ Interpolation = new(VariableInterpolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVariableInterpolation(t *testing.T) {
|
||||||
|
uv, err := NewUserVariable("var.foo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
i := &VariableInterpolation{Variable: uv, key: "var.foo"}
|
||||||
|
if i.FullString() != "var.foo" {
|
||||||
|
t.Fatalf("err: %#v", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := map[string]InterpolatedVariable{"var.foo": uv}
|
||||||
|
if !reflect.DeepEqual(i.Variables(), expected) {
|
||||||
|
t.Fatalf("bad: %#v", i.Variables())
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := i.Interpolate(map[string]string{
|
||||||
|
"var.foo": "bar",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if actual != "bar" {
|
||||||
|
t.Fatalf("bad: %#v", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVariableInterpolation_missing(t *testing.T) {
|
||||||
|
uv, err := NewUserVariable("var.foo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
i := &VariableInterpolation{Variable: uv, key: "var.foo"}
|
||||||
|
_, err = i.Interpolate(map[string]string{
|
||||||
|
"var.bar": "bar",
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should error")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,151 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mitchellh/reflectwalk"
|
||||||
|
)
|
||||||
|
|
||||||
|
// interpRegexp is a regexp that matches interpolations such as ${foo.bar}
|
||||||
|
var interpRegexp *regexp.Regexp = regexp.MustCompile(
|
||||||
|
`(?i)(\$+)\{([\s*-.,\(\)a-z0-9_]+)\}`)
|
||||||
|
|
||||||
|
// interpolationWalker implements interfaces for the reflectwalk package
|
||||||
|
// (github.com/mitchellh/reflectwalk) that can be used to automatically
|
||||||
|
// execute a callback for an interpolation.
|
||||||
|
type interpolationWalker struct {
|
||||||
|
F interpolationWalkerFunc
|
||||||
|
Replace bool
|
||||||
|
|
||||||
|
key []string
|
||||||
|
loc reflectwalk.Location
|
||||||
|
cs []reflect.Value
|
||||||
|
csData interface{}
|
||||||
|
unknownKeys []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// interpolationWalkerFunc is the callback called by interpolationWalk.
|
||||||
|
// It is called with any interpolation found. It should return a value
|
||||||
|
// to replace the interpolation with, along with any errors.
|
||||||
|
//
|
||||||
|
// If Replace is set to false in interpolationWalker, then the replace
|
||||||
|
// value can be anything as it will have no effect.
|
||||||
|
type interpolationWalkerFunc func(Interpolation) (string, error)
|
||||||
|
|
||||||
|
func (w *interpolationWalker) Enter(loc reflectwalk.Location) error {
|
||||||
|
w.loc = loc
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *interpolationWalker) Exit(loc reflectwalk.Location) error {
|
||||||
|
w.loc = reflectwalk.None
|
||||||
|
|
||||||
|
switch loc {
|
||||||
|
case reflectwalk.Map:
|
||||||
|
w.cs = w.cs[:len(w.cs)-1]
|
||||||
|
case reflectwalk.MapValue:
|
||||||
|
w.key = w.key[:len(w.key)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *interpolationWalker) Map(m reflect.Value) error {
|
||||||
|
w.cs = append(w.cs, m)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *interpolationWalker) MapElem(m, k, v reflect.Value) error {
|
||||||
|
w.csData = k
|
||||||
|
w.key = append(w.key, k.String())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *interpolationWalker) Primitive(v reflect.Value) error {
|
||||||
|
setV := v
|
||||||
|
|
||||||
|
// We only care about strings
|
||||||
|
if v.Kind() == reflect.Interface {
|
||||||
|
setV = v
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
if v.Kind() != reflect.String {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX: This can be a lot more efficient if we used a real
|
||||||
|
// parser. A regexp is a hammer though that will get this working.
|
||||||
|
|
||||||
|
matches := interpRegexp.FindAllStringSubmatch(v.String(), -1)
|
||||||
|
if len(matches) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result := v.String()
|
||||||
|
for _, match := range matches {
|
||||||
|
dollars := len(match[1])
|
||||||
|
|
||||||
|
// If there are even amounts of dollar signs, then it is escaped
|
||||||
|
if dollars%2 == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interpolation found, instantiate it
|
||||||
|
key := match[2]
|
||||||
|
|
||||||
|
i, err := NewInterpolation(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceVal, err := w.F(i)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.Replace {
|
||||||
|
// If this is an unknown variable, then we remove it from
|
||||||
|
// the configuration.
|
||||||
|
if replaceVal == UnknownVariableValue {
|
||||||
|
w.removeCurrent()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result = strings.Replace(result, match[0], replaceVal, -1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.Replace {
|
||||||
|
resultVal := reflect.ValueOf(result)
|
||||||
|
if w.loc == reflectwalk.MapValue {
|
||||||
|
// If we're in a map, then the only way to set a map value is
|
||||||
|
// to set it directly.
|
||||||
|
m := w.cs[len(w.cs)-1]
|
||||||
|
mk := w.csData.(reflect.Value)
|
||||||
|
m.SetMapIndex(mk, resultVal)
|
||||||
|
} else {
|
||||||
|
// Otherwise, we should be addressable
|
||||||
|
setV.Set(resultVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *interpolationWalker) removeCurrent() {
|
||||||
|
c := w.cs[len(w.cs)-1]
|
||||||
|
switch c.Kind() {
|
||||||
|
case reflect.Map:
|
||||||
|
// Zero value so that we delete the map key
|
||||||
|
var val reflect.Value
|
||||||
|
|
||||||
|
// Get the key and delete it
|
||||||
|
k := w.csData.(reflect.Value)
|
||||||
|
c.SetMapIndex(k, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append the key to the unknown keys
|
||||||
|
w.unknownKeys = append(w.unknownKeys, strings.Join(w.key, "."))
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mitchellh/reflectwalk"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInterpolationWalker_detect(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Input interface{}
|
||||||
|
Result []Interpolation
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Input: map[string]interface{}{
|
||||||
|
"foo": "$${var.foo}",
|
||||||
|
},
|
||||||
|
Result: nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
Input: map[string]interface{}{
|
||||||
|
"foo": "${var.foo}",
|
||||||
|
},
|
||||||
|
Result: []Interpolation{
|
||||||
|
&VariableInterpolation{
|
||||||
|
Variable: &UserVariable{
|
||||||
|
Name: "foo",
|
||||||
|
key: "var.foo",
|
||||||
|
},
|
||||||
|
key: "var.foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
Input: map[string]interface{}{
|
||||||
|
"foo": "${lookup(var.foo)}",
|
||||||
|
},
|
||||||
|
Result: []Interpolation{
|
||||||
|
&FunctionInterpolation{
|
||||||
|
Func: nil,
|
||||||
|
Args: []InterpolatedVariable{
|
||||||
|
&UserVariable{
|
||||||
|
Name: "foo",
|
||||||
|
key: "var.foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
key: "lookup(var.foo)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
var actual []Interpolation
|
||||||
|
|
||||||
|
detectFn := func(i Interpolation) (string, error) {
|
||||||
|
actual = append(actual, i)
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w := &interpolationWalker{F: detectFn}
|
||||||
|
if err := reflectwalk.Walk(tc.Input, w); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, a := range actual {
|
||||||
|
// This is jank, but reflect.DeepEqual never has functions
|
||||||
|
// being the same.
|
||||||
|
if f, ok := a.(*FunctionInterpolation); ok {
|
||||||
|
f.Func = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(actual, tc.Result) {
|
||||||
|
t.Fatalf("%d: bad:\n\n%#v", i, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInterpolationWalker_replace(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Input interface{}
|
||||||
|
Output interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Input: map[string]interface{}{
|
||||||
|
"foo": "$${var.foo}",
|
||||||
|
},
|
||||||
|
Output: map[string]interface{}{
|
||||||
|
"foo": "$${var.foo}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
Input: map[string]interface{}{
|
||||||
|
"foo": "hello, ${var.foo}",
|
||||||
|
},
|
||||||
|
Output: map[string]interface{}{
|
||||||
|
"foo": "hello, bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
fn := func(i Interpolation) (string, error) {
|
||||||
|
return "bar", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w := &interpolationWalker{F: fn, Replace: true}
|
||||||
|
if err := reflectwalk.Walk(tc.Input, w); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(tc.Input, tc.Output) {
|
||||||
|
t.Fatalf("%d: bad:\n\n%#v", i, tc.Input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,7 +30,7 @@ func (t *libuclConfigurable) Config() (*Config, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type LibuclVariable struct {
|
type LibuclVariable struct {
|
||||||
Default string
|
Default interface{}
|
||||||
Description string
|
Description string
|
||||||
Fields []string `libucl:",decodedFields"`
|
Fields []string `libucl:",decodedFields"`
|
||||||
}
|
}
|
||||||
|
@ -51,20 +51,27 @@ func (t *libuclConfigurable) Config() (*Config, error) {
|
||||||
if len(rawConfig.Variable) > 0 {
|
if len(rawConfig.Variable) > 0 {
|
||||||
config.Variables = make([]*Variable, 0, len(rawConfig.Variable))
|
config.Variables = make([]*Variable, 0, len(rawConfig.Variable))
|
||||||
for k, v := range rawConfig.Variable {
|
for k, v := range rawConfig.Variable {
|
||||||
defaultSet := false
|
// Defaults turn into a slice of map[string]interface{} and
|
||||||
for _, f := range v.Fields {
|
// we need to make sure to convert that down into the
|
||||||
if f == "Default" {
|
// proper type for Config.
|
||||||
defaultSet = true
|
if ms, ok := v.Default.([]map[string]interface{}); ok {
|
||||||
break
|
def := make(map[string]interface{})
|
||||||
|
for _, m := range ms {
|
||||||
|
for k, v := range m {
|
||||||
|
def[k] = v
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
v.Default = def
|
||||||
}
|
}
|
||||||
|
|
||||||
config.Variables = append(config.Variables, &Variable{
|
newVar := &Variable{
|
||||||
Name: k,
|
Name: k,
|
||||||
Default: v.Default,
|
Default: v.Default,
|
||||||
Description: v.Description,
|
Description: v.Description,
|
||||||
defaultSet: defaultSet,
|
}
|
||||||
})
|
|
||||||
|
config.Variables = append(config.Variables, newVar)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -437,20 +437,20 @@ func variablesStr(vs []*Variable) string {
|
||||||
for _, k := range ks {
|
for _, k := range ks {
|
||||||
v := m[k]
|
v := m[k]
|
||||||
|
|
||||||
if v.Default == "" {
|
required := ""
|
||||||
|
if v.Required() {
|
||||||
|
required = " (required)"
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Default == nil || v.Default == "" {
|
||||||
v.Default = "<>"
|
v.Default = "<>"
|
||||||
}
|
}
|
||||||
if v.Description == "" {
|
if v.Description == "" {
|
||||||
v.Description = "<>"
|
v.Description = "<>"
|
||||||
}
|
}
|
||||||
|
|
||||||
required := ""
|
|
||||||
if v.Required() {
|
|
||||||
required = " (required)"
|
|
||||||
}
|
|
||||||
|
|
||||||
result += fmt.Sprintf(
|
result += fmt.Sprintf(
|
||||||
"%s%s\n %s\n %s\n",
|
"%s%s\n %v\n %s\n",
|
||||||
k,
|
k,
|
||||||
required,
|
required,
|
||||||
v.Default,
|
v.Default,
|
||||||
|
|
|
@ -103,7 +103,7 @@ func TestMerge(t *testing.T) {
|
||||||
&Resource{Name: "bar"},
|
&Resource{Name: "bar"},
|
||||||
},
|
},
|
||||||
Variables: []*Variable{
|
Variables: []*Variable{
|
||||||
&Variable{Name: "foo", Default: "bar", defaultSet: true},
|
&Variable{Name: "foo", Default: "bar"},
|
||||||
&Variable{Name: "bar"},
|
&Variable{Name: "bar"},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -124,7 +124,7 @@ func TestMerge(t *testing.T) {
|
||||||
&Resource{Name: "bar"},
|
&Resource{Name: "bar"},
|
||||||
},
|
},
|
||||||
Variables: []*Variable{
|
Variables: []*Variable{
|
||||||
&Variable{Name: "foo", Default: "bar", defaultSet: true},
|
&Variable{Name: "foo", Default: "bar"},
|
||||||
&Variable{Name: "foo"},
|
&Variable{Name: "foo"},
|
||||||
&Variable{Name: "bar"},
|
&Variable{Name: "bar"},
|
||||||
},
|
},
|
||||||
|
|
|
@ -69,26 +69,43 @@ func (r *RawConfig) Interpolate(vs map[string]string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
w := &variableReplaceWalker{Values: vs}
|
|
||||||
r.config = config.(map[string]interface{})
|
r.config = config.(map[string]interface{})
|
||||||
|
|
||||||
|
fn := func(i Interpolation) (string, error) {
|
||||||
|
return i.Interpolate(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
w := &interpolationWalker{F: fn, Replace: true}
|
||||||
err = reflectwalk.Walk(r.config, w)
|
err = reflectwalk.Walk(r.config, w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.unknownKeys = w.UnknownKeys
|
r.unknownKeys = w.unknownKeys
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RawConfig) init() error {
|
func (r *RawConfig) init() error {
|
||||||
walker := new(variableDetectWalker)
|
r.config = r.Raw
|
||||||
|
r.Variables = nil
|
||||||
|
|
||||||
|
fn := func(i Interpolation) (string, error) {
|
||||||
|
for k, v := range i.Variables() {
|
||||||
|
if r.Variables == nil {
|
||||||
|
r.Variables = make(map[string]InterpolatedVariable)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Variables[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
walker := &interpolationWalker{F: fn}
|
||||||
if err := reflectwalk.Walk(r.Raw, walker); err != nil {
|
if err := reflectwalk.Walk(r.Raw, walker); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Variables = walker.Variables
|
|
||||||
r.config = r.Raw
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,12 @@ variable "foo" {
|
||||||
description = "bar";
|
description = "bar";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "amis" {
|
||||||
|
default = {
|
||||||
|
"east": "foo",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
provider "aws" {
|
provider "aws" {
|
||||||
access_key = "foo";
|
access_key = "foo";
|
||||||
secret_key = "bar";
|
secret_key = "bar";
|
||||||
|
@ -16,7 +22,7 @@ resource "aws_security_group" "firewall" {
|
||||||
}
|
}
|
||||||
|
|
||||||
resource aws_instance "web" {
|
resource aws_instance "web" {
|
||||||
ami = "${var.foo}"
|
ami = "${var.amis.east}"
|
||||||
security_groups = [
|
security_groups = [
|
||||||
"foo",
|
"foo",
|
||||||
"${aws_security_group.firewall.foo}"
|
"${aws_security_group.firewall.foo}"
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
variable "foo" {
|
||||||
|
default = ["foo", "bar"]
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
variable "foo" {
|
||||||
|
default = "bar"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "foo" {
|
||||||
|
default = {
|
||||||
|
"foo" = "bar"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,216 +0,0 @@
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/mitchellh/reflectwalk"
|
|
||||||
)
|
|
||||||
|
|
||||||
// varRegexp is a regexp that matches variables such as ${foo.bar}
|
|
||||||
var varRegexp *regexp.Regexp
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
varRegexp = regexp.MustCompile(`(?i)(\$+)\{([*-.a-z0-9_]+)\}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReplaceVariables takes a configuration and a mapping of variables
|
|
||||||
// and performs the structure walking necessary to properly replace
|
|
||||||
// all the variables.
|
|
||||||
func ReplaceVariables(
|
|
||||||
c interface{},
|
|
||||||
vs map[string]string) ([]string, error) {
|
|
||||||
w := &variableReplaceWalker{Values: vs}
|
|
||||||
if err := reflectwalk.Walk(c, w); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return w.UnknownKeys, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// variableDetectWalker implements interfaces for the reflectwalk package
|
|
||||||
// (github.com/mitchellh/reflectwalk) that can be used to automatically
|
|
||||||
// pull out the variables that need replacing.
|
|
||||||
type variableDetectWalker struct {
|
|
||||||
Variables map[string]InterpolatedVariable
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *variableDetectWalker) Primitive(v reflect.Value) error {
|
|
||||||
// We only care about strings
|
|
||||||
if v.Kind() == reflect.Interface {
|
|
||||||
v = v.Elem()
|
|
||||||
}
|
|
||||||
if v.Kind() != reflect.String {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// XXX: This can be a lot more efficient if we used a real
|
|
||||||
// parser. A regexp is a hammer though that will get this working.
|
|
||||||
|
|
||||||
matches := varRegexp.FindAllStringSubmatch(v.String(), -1)
|
|
||||||
if len(matches) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, match := range matches {
|
|
||||||
dollars := len(match[1])
|
|
||||||
|
|
||||||
// If there are even amounts of dollar signs, then it is escaped
|
|
||||||
if dollars%2 == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, record it
|
|
||||||
key := match[2]
|
|
||||||
if w.Variables == nil {
|
|
||||||
w.Variables = make(map[string]InterpolatedVariable)
|
|
||||||
}
|
|
||||||
if _, ok := w.Variables[key]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
var iv InterpolatedVariable
|
|
||||||
if strings.Index(key, ".") == -1 {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"Interpolated variable '%s' has bad format. "+
|
|
||||||
"Did you mean 'var.%s'?",
|
|
||||||
key, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(key, "var.") {
|
|
||||||
iv, err = NewUserVariable(key)
|
|
||||||
} else {
|
|
||||||
iv, err = NewResourceVariable(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Variables[key] = iv
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// variableReplaceWalker implements interfaces for reflectwalk that
|
|
||||||
// is used to replace variables with their values.
|
|
||||||
//
|
|
||||||
// If Values does not have every available value, then the program
|
|
||||||
// will _panic_. The variableDetectWalker will tell you all variables
|
|
||||||
// you need.
|
|
||||||
type variableReplaceWalker struct {
|
|
||||||
Values map[string]string
|
|
||||||
UnknownKeys []string
|
|
||||||
|
|
||||||
key []string
|
|
||||||
loc reflectwalk.Location
|
|
||||||
cs []reflect.Value
|
|
||||||
csData interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *variableReplaceWalker) Enter(loc reflectwalk.Location) error {
|
|
||||||
w.loc = loc
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *variableReplaceWalker) Exit(loc reflectwalk.Location) error {
|
|
||||||
w.loc = reflectwalk.None
|
|
||||||
|
|
||||||
switch loc {
|
|
||||||
case reflectwalk.Map:
|
|
||||||
w.cs = w.cs[:len(w.cs)-1]
|
|
||||||
case reflectwalk.MapValue:
|
|
||||||
w.key = w.key[:len(w.key)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *variableReplaceWalker) Map(m reflect.Value) error {
|
|
||||||
w.cs = append(w.cs, m)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *variableReplaceWalker) MapElem(m, k, v reflect.Value) error {
|
|
||||||
w.csData = k
|
|
||||||
w.key = append(w.key, k.String())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *variableReplaceWalker) Primitive(v reflect.Value) error {
|
|
||||||
// We only care about strings
|
|
||||||
setV := v
|
|
||||||
if v.Kind() == reflect.Interface {
|
|
||||||
setV = v
|
|
||||||
v = v.Elem()
|
|
||||||
}
|
|
||||||
if v.Kind() != reflect.String {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
matches := varRegexp.FindAllStringSubmatch(v.String(), -1)
|
|
||||||
if len(matches) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
result := v.String()
|
|
||||||
for _, match := range matches {
|
|
||||||
dollars := len(match[1])
|
|
||||||
|
|
||||||
// If there are even amounts of dollar signs, then it is escaped
|
|
||||||
if dollars%2 == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the key
|
|
||||||
key := match[2]
|
|
||||||
value, ok := w.Values[key]
|
|
||||||
if !ok {
|
|
||||||
panic("no value for variable key: " + key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is an unknown variable, then we remove it from
|
|
||||||
// the configuration.
|
|
||||||
if value == UnknownVariableValue {
|
|
||||||
w.removeCurrent()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace
|
|
||||||
result = strings.Replace(result, match[0], value, -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
resultVal := reflect.ValueOf(result)
|
|
||||||
if w.loc == reflectwalk.MapValue {
|
|
||||||
// If we're in a map, then the only way to set a map value is
|
|
||||||
// to set it directly.
|
|
||||||
m := w.cs[len(w.cs)-1]
|
|
||||||
mk := w.csData.(reflect.Value)
|
|
||||||
m.SetMapIndex(mk, resultVal)
|
|
||||||
} else {
|
|
||||||
// Otherwise, we should be addressable
|
|
||||||
setV.Set(resultVal)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *variableReplaceWalker) removeCurrent() {
|
|
||||||
c := w.cs[len(w.cs)-1]
|
|
||||||
switch c.Kind() {
|
|
||||||
case reflect.Map:
|
|
||||||
// Zero value so that we delete the map key
|
|
||||||
var val reflect.Value
|
|
||||||
|
|
||||||
// Get the key and delete it
|
|
||||||
k := w.csData.(reflect.Value)
|
|
||||||
c.SetMapIndex(k, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append the key to the unknown keys
|
|
||||||
w.UnknownKeys = append(w.UnknownKeys, strings.Join(w.key, "."))
|
|
||||||
}
|
|
|
@ -1,282 +0,0 @@
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/mitchellh/reflectwalk"
|
|
||||||
)
|
|
||||||
|
|
||||||
func BenchmarkVariableDetectWalker(b *testing.B) {
|
|
||||||
w := new(variableDetectWalker)
|
|
||||||
str := reflect.ValueOf(`foo ${var.bar} bar ${bar.baz.bing} $${escaped}`)
|
|
||||||
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
w.Variables = nil
|
|
||||||
w.Primitive(str)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkVariableReplaceWalker(b *testing.B) {
|
|
||||||
w := &variableReplaceWalker{
|
|
||||||
Values: map[string]string{
|
|
||||||
"var.bar": "bar",
|
|
||||||
"bar.baz.bing": "baz",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
str := `foo ${var.bar} bar ${bar.baz.bing} $${escaped}`
|
|
||||||
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
if err := reflectwalk.Walk(&str, w); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReplaceVariables(t *testing.T) {
|
|
||||||
input := "foo-${var.bar}"
|
|
||||||
expected := "foo-bar"
|
|
||||||
|
|
||||||
unk, err := ReplaceVariables(&input, map[string]string{
|
|
||||||
"var.bar": "bar",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
if len(unk) > 0 {
|
|
||||||
t.Fatal("bad: %#v", unk)
|
|
||||||
}
|
|
||||||
|
|
||||||
if input != expected {
|
|
||||||
t.Fatalf("bad: %#v", input)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVariableDetectWalker(t *testing.T) {
|
|
||||||
w := new(variableDetectWalker)
|
|
||||||
|
|
||||||
str := `foo ${var.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)
|
|
||||||
}
|
|
||||||
if w.Variables["var.bar"].(*UserVariable).FullKey() != "var.bar" {
|
|
||||||
t.Fatalf("bad: %#v", w.Variables)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVariableDetectWalker_resource(t *testing.T) {
|
|
||||||
w := new(variableDetectWalker)
|
|
||||||
|
|
||||||
str := `foo ${ec2.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)
|
|
||||||
}
|
|
||||||
if w.Variables["ec2.foo.bar"].(*ResourceVariable).FullKey() != "ec2.foo.bar" {
|
|
||||||
t.Fatalf("bad: %#v", w.Variables)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVariableDetectWalker_resourceMulti(t *testing.T) {
|
|
||||||
w := new(variableDetectWalker)
|
|
||||||
|
|
||||||
str := `foo ${ec2.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)
|
|
||||||
}
|
|
||||||
if w.Variables["ec2.foo.*.bar"].(*ResourceVariable).FullKey() != "ec2.foo.*.bar" {
|
|
||||||
t.Fatalf("bad: %#v", w.Variables)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVariableDetectWalker_bad(t *testing.T) {
|
|
||||||
w := new(variableDetectWalker)
|
|
||||||
|
|
||||||
str := `foo ${bar}`
|
|
||||||
if err := w.Primitive(reflect.ValueOf(str)); err == nil {
|
|
||||||
t.Fatal("should error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVariableDetectWalker_escaped(t *testing.T) {
|
|
||||||
w := new(variableDetectWalker)
|
|
||||||
|
|
||||||
str := `foo $${var.bar}`
|
|
||||||
if err := w.Primitive(reflect.ValueOf(str)); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(w.Variables) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", w.Variables)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVariableDetectWalker_empty(t *testing.T) {
|
|
||||||
w := new(variableDetectWalker)
|
|
||||||
|
|
||||||
str := `foo`
|
|
||||||
if err := w.Primitive(reflect.ValueOf(str)); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(w.Variables) > 0 {
|
|
||||||
t.Fatalf("bad: %#v", w.Variables)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVariableReplaceWalker(t *testing.T) {
|
|
||||||
w := &variableReplaceWalker{
|
|
||||||
Values: map[string]string{
|
|
||||||
"var.bar": "bar",
|
|
||||||
"var.unknown": UnknownVariableValue,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
Input interface{}
|
|
||||||
Output interface{}
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
`foo ${var.bar}`,
|
|
||||||
"foo bar",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]string{"foo", "${var.bar}"},
|
|
||||||
[]string{"foo", "bar"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"ami": "${var.bar}",
|
|
||||||
"security_groups": []interface{}{
|
|
||||||
"foo",
|
|
||||||
"${var.bar}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"ami": "bar",
|
|
||||||
"security_groups": []interface{}{
|
|
||||||
"foo",
|
|
||||||
"bar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"foo": map[string]interface{}{
|
|
||||||
"foo": []string{"${var.bar}"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"foo": map[string]interface{}{
|
|
||||||
"foo": []string{"bar"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"foo": "bar",
|
|
||||||
"bar": "hello${var.unknown}world",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"foo": "bar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"foo": []string{"foo", "${var.unknown}", "bar"},
|
|
||||||
},
|
|
||||||
map[string]interface{}{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, tc := range cases {
|
|
||||||
var input interface{} = tc.Input
|
|
||||||
if reflect.ValueOf(tc.Input).Kind() == reflect.String {
|
|
||||||
input = &tc.Input
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := reflectwalk.Walk(input, w); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(tc.Input, tc.Output) {
|
|
||||||
t.Fatalf("bad %d: %#v", i, tc.Input)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVariableReplaceWalker_unknown(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
Input interface{}
|
|
||||||
Output interface{}
|
|
||||||
Keys []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"foo": "bar",
|
|
||||||
"bar": "hello${var.unknown}world",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"foo": "bar",
|
|
||||||
},
|
|
||||||
[]string{"bar"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"foo": []string{"foo", "${var.unknown}", "bar"},
|
|
||||||
},
|
|
||||||
map[string]interface{}{},
|
|
||||||
[]string{"foo"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"foo": map[string]interface{}{
|
|
||||||
"bar": "${var.unknown}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"foo": map[string]interface{}{},
|
|
||||||
},
|
|
||||||
[]string{"foo.bar"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, tc := range cases {
|
|
||||||
var input interface{} = tc.Input
|
|
||||||
w := &variableReplaceWalker{
|
|
||||||
Values: map[string]string{
|
|
||||||
"var.unknown": UnknownVariableValue,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if reflect.ValueOf(tc.Input).Kind() == reflect.String {
|
|
||||||
input = &tc.Input
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := reflectwalk.Walk(input, w); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(tc.Input, tc.Output) {
|
|
||||||
t.Fatalf("bad %d: %#v", i, tc.Input)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(tc.Keys, w.UnknownKeys) {
|
|
||||||
t.Fatalf("bad: %#v", w.UnknownKeys)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -23,6 +23,10 @@ func (e *Error) Error() string {
|
||||||
len(e.Errors), strings.Join(points, "\n"))
|
len(e.Errors), strings.Join(points, "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Error) GoString() string {
|
||||||
|
return fmt.Sprintf("*%#v", *e)
|
||||||
|
}
|
||||||
|
|
||||||
// ErrorAppend is a helper function that will append more errors
|
// ErrorAppend is a helper function that will append more errors
|
||||||
// onto a Error in order to create a larger multi-error. If the
|
// onto a Error in order to create a larger multi-error. If the
|
||||||
// original error is not a Error, it will be turned into one.
|
// original error is not a Error, it will be turned into one.
|
||||||
|
|
|
@ -30,6 +30,7 @@ type Context struct {
|
||||||
providers map[string]ResourceProviderFactory
|
providers map[string]ResourceProviderFactory
|
||||||
provisioners map[string]ResourceProvisionerFactory
|
provisioners map[string]ResourceProvisionerFactory
|
||||||
variables map[string]string
|
variables map[string]string
|
||||||
|
defaultVars map[string]string
|
||||||
|
|
||||||
l sync.Mutex // Lock acquired during any task
|
l sync.Mutex // Lock acquired during any task
|
||||||
parCh chan struct{} // Semaphore used to limit parallelism
|
parCh chan struct{} // Semaphore used to limit parallelism
|
||||||
|
@ -72,6 +73,14 @@ func NewContext(opts *ContextOpts) *Context {
|
||||||
}
|
}
|
||||||
parCh := make(chan struct{}, par)
|
parCh := make(chan struct{}, par)
|
||||||
|
|
||||||
|
// Calculate all the default variables
|
||||||
|
defaultVars := make(map[string]string)
|
||||||
|
for _, v := range opts.Config.Variables {
|
||||||
|
for k, val := range v.DefaultsMap() {
|
||||||
|
defaultVars[k] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &Context{
|
return &Context{
|
||||||
config: opts.Config,
|
config: opts.Config,
|
||||||
diff: opts.Diff,
|
diff: opts.Diff,
|
||||||
|
@ -80,6 +89,7 @@ func NewContext(opts *ContextOpts) *Context {
|
||||||
providers: opts.Providers,
|
providers: opts.Providers,
|
||||||
provisioners: opts.Provisioners,
|
provisioners: opts.Provisioners,
|
||||||
variables: opts.Variables,
|
variables: opts.Variables,
|
||||||
|
defaultVars: defaultVars,
|
||||||
|
|
||||||
parCh: parCh,
|
parCh: parCh,
|
||||||
sh: sh,
|
sh: sh,
|
||||||
|
@ -296,8 +306,13 @@ func (c *Context) computeVars(raw *config.RawConfig) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Go through each variable and find it
|
// Start building up the variables. First, defaults
|
||||||
vs := make(map[string]string)
|
vs := make(map[string]string)
|
||||||
|
for k, v := range c.defaultVars {
|
||||||
|
vs[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, the actual computed variables
|
||||||
for n, rawV := range raw.Variables {
|
for n, rawV := range raw.Variables {
|
||||||
switch v := rawV.(type) {
|
switch v := rawV.(type) {
|
||||||
case *config.ResourceVariable:
|
case *config.ResourceVariable:
|
||||||
|
@ -314,7 +329,19 @@ func (c *Context) computeVars(raw *config.RawConfig) error {
|
||||||
|
|
||||||
vs[n] = attr
|
vs[n] = attr
|
||||||
case *config.UserVariable:
|
case *config.UserVariable:
|
||||||
vs[n] = c.variables[v.Name]
|
val, ok := c.variables[v.Name]
|
||||||
|
if ok {
|
||||||
|
vs[n] = val
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look up if we have any variables with this prefix because
|
||||||
|
// those are map overrides. Include those.
|
||||||
|
for k, val := range c.variables {
|
||||||
|
if strings.HasPrefix(k, v.Name+".") {
|
||||||
|
vs["var."+k] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1045,10 +1045,19 @@ func TestContextApply_vars(t *testing.T) {
|
||||||
"aws": testProviderFuncFixed(p),
|
"aws": testProviderFuncFixed(p),
|
||||||
},
|
},
|
||||||
Variables: map[string]string{
|
Variables: map[string]string{
|
||||||
"foo": "bar",
|
"foo": "us-west-2",
|
||||||
|
"amis.us-east-1": "override",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
w, e := ctx.Validate()
|
||||||
|
if len(w) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", w)
|
||||||
|
}
|
||||||
|
if len(e) > 0 {
|
||||||
|
t.Fatalf("bad: %s", e)
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := ctx.Plan(nil); err != nil {
|
if _, err := ctx.Plan(nil); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -1159,10 +1168,6 @@ func TestContextPlan_computed(t *testing.T) {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(plan.Diff.Resources) < 2 {
|
|
||||||
t.Fatalf("bad: %#v", plan.Diff.Resources)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := strings.TrimSpace(plan.String())
|
actual := strings.TrimSpace(plan.String())
|
||||||
expected := strings.TrimSpace(testTerraformPlanComputedStr)
|
expected := strings.TrimSpace(testTerraformPlanComputedStr)
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
|
|
|
@ -18,4 +18,13 @@ func TestSMCUserVariables(t *testing.T) {
|
||||||
if len(errs) != 0 {
|
if len(errs) != 0 {
|
||||||
t.Fatalf("err: %#v", errs)
|
t.Fatalf("err: %#v", errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mapping element override
|
||||||
|
errs = smcUserVariables(c, map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"map.foo": "baz",
|
||||||
|
})
|
||||||
|
if len(errs) != 0 {
|
||||||
|
t.Fatalf("err: %#v", errs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -221,10 +221,13 @@ aws_instance.foo:
|
||||||
const testTerraformApplyVarsStr = `
|
const testTerraformApplyVarsStr = `
|
||||||
aws_instance.bar:
|
aws_instance.bar:
|
||||||
ID = foo
|
ID = foo
|
||||||
foo = bar
|
bar = foo
|
||||||
|
baz = override
|
||||||
|
foo = us-west-2
|
||||||
type = aws_instance
|
type = aws_instance
|
||||||
aws_instance.foo:
|
aws_instance.foo:
|
||||||
ID = foo
|
ID = foo
|
||||||
|
bar = baz
|
||||||
num = 2
|
num = 2
|
||||||
type = aws_instance
|
type = aws_instance
|
||||||
`
|
`
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
variable "value" {
|
||||||
|
default = ""
|
||||||
|
}
|
||||||
|
|
||||||
resource "aws_instance" "foo" {
|
resource "aws_instance" "foo" {
|
||||||
num = "2"
|
num = "2"
|
||||||
compute = "dynamical"
|
compute = "dynamical"
|
||||||
|
|
|
@ -1,7 +1,23 @@
|
||||||
|
variable "amis" {
|
||||||
|
default = {
|
||||||
|
"us-east-1": "foo",
|
||||||
|
"us-west-2": "foo",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "bar" {
|
||||||
|
default = "baz"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "foo" {}
|
||||||
|
|
||||||
resource "aws_instance" "foo" {
|
resource "aws_instance" "foo" {
|
||||||
num = "2"
|
num = "2"
|
||||||
|
bar = "${var.bar}"
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_instance" "bar" {
|
resource "aws_instance" "bar" {
|
||||||
foo = "${var.foo}"
|
foo = "${var.foo}"
|
||||||
|
bar = "${lookup(var.amis, var.foo)}"
|
||||||
|
baz = "${var.amis.us-east-1}"
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,3 +6,10 @@ variable "foo" {
|
||||||
variable "bar" {
|
variable "bar" {
|
||||||
default = "baz"
|
default = "baz"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Mapping
|
||||||
|
variable "map" {
|
||||||
|
default = {
|
||||||
|
"foo" = "bar";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue