config: validate that multi-variables are only used in slices

This commit is contained in:
Mitchell Hashimoto 2014-10-09 21:15:08 -07:00
parent 75e79da9c3
commit 7b48924532
4 changed files with 88 additions and 14 deletions

View File

@ -388,6 +388,17 @@ func (c *Config) Validate() error {
}
}
// Check that all variables are in the proper context
for source, rc := range c.rawConfigs() {
walker := &interpolationWalker{
ContextF: c.validateVarContextFn(source, &errs),
}
if err := reflectwalk.Walk(rc.Raw, walker); err != nil {
errs = append(errs, fmt.Errorf(
"%s: error reading config: %s", source, err))
}
}
if len(errs) > 0 {
return &multierror.Error{Errors: errs}
}
@ -400,33 +411,57 @@ func (c *Config) Validate() error {
// are valid in the Validate step.
func (c *Config) InterpolatedVariables() map[string][]InterpolatedVariable {
result := make(map[string][]InterpolatedVariable)
for _, pc := range c.ProviderConfigs {
source := fmt.Sprintf("provider config '%s'", pc.Name)
for _, v := range pc.RawConfig.Variables {
for source, rc := range c.rawConfigs() {
for _, v := range rc.Variables {
result[source] = append(result[source], v)
}
}
return result
}
// rawConfigs returns all of the RawConfigs that are available keyed by
// a human-friendly source.
func (c *Config) rawConfigs() map[string]*RawConfig {
result := make(map[string]*RawConfig)
for _, pc := range c.ProviderConfigs {
source := fmt.Sprintf("provider config '%s'", pc.Name)
result[source] = pc.RawConfig
}
for _, rc := range c.Resources {
source := fmt.Sprintf("resource '%s'", rc.Id())
for _, v := range rc.RawCount.Variables {
result[source] = append(result[source], v)
}
for _, v := range rc.RawConfig.Variables {
result[source] = append(result[source], v)
}
result[source+" count"] = rc.RawCount
result[source+" config"] = rc.RawConfig
}
for _, o := range c.Outputs {
source := fmt.Sprintf("output '%s'", o.Name)
for _, v := range o.RawConfig.Variables {
result[source] = append(result[source], v)
}
result[source] = o.RawConfig
}
return result
}
func (c *Config) validateVarContextFn(
source string, errs *[]error) interpolationWalkerContextFunc {
return func(loc reflectwalk.Location, i Interpolation) {
vi, ok := i.(*VariableInterpolation)
if !ok {
return
}
rv, ok := vi.Variable.(*ResourceVariable)
if !ok {
return
}
if rv.Multi && loc != reflectwalk.SliceElem {
*errs = append(*errs, fmt.Errorf(
"%s: multi-variable must be in a slice", source))
}
}
}
func (m *Module) mergerName() string {
return m.Id()
}

View File

@ -221,6 +221,13 @@ func TestConfigValidate_varDefaultInterpolate(t *testing.T) {
}
}
func TestConfigValidate_varMultiNonSlice(t *testing.T) {
c := testConfig(t, "validate-var-multi-non-slice")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_varModule(t *testing.T) {
c := testConfig(t, "validate-var-module")
if err := c.Validate(); err != nil {

View File

@ -22,9 +22,18 @@ var interpRegexp *regexp.Regexp = regexp.MustCompile(
// (github.com/mitchellh/reflectwalk) that can be used to automatically
// execute a callback for an interpolation.
type interpolationWalker struct {
// F is the function to call for every interpolation. It can be nil.
//
// If Replace is true, then the return value of F will be used to
// replace the interpolation.
F interpolationWalkerFunc
Replace bool
// ContextF is an advanced version of F that also receives the
// location of where it is in the structure. This lets you do
// context-aware validation.
ContextF interpolationWalkerContextFunc
key []string
lastValue reflect.Value
loc reflectwalk.Location
@ -43,6 +52,14 @@ type interpolationWalker struct {
// value can be anything as it will have no effect.
type interpolationWalkerFunc func(Interpolation) (string, error)
// interpolationWalkerContextFunc is called by interpolationWalk if
// ContextF is set. This receives both the interpolation and the location
// where the interpolation is.
//
// This callback can be used to validate the location of the interpolation
// within the configuration.
type interpolationWalkerContextFunc func(reflectwalk.Location, Interpolation)
func (w *interpolationWalker) Enter(loc reflectwalk.Location) error {
w.loc = loc
return nil
@ -129,6 +146,14 @@ func (w *interpolationWalker) Primitive(v reflect.Value) error {
return err
}
if w.ContextF != nil {
w.ContextF(w.loc, i)
}
if w.F == nil {
continue
}
replaceVal, err := w.F(i)
if err != nil {
return fmt.Errorf(

View File

@ -0,0 +1,7 @@
resource "aws_instance" "foo" {
count = 3
}
resource "aws_instance" "bar" {
foo = "${aws_instance.foo.*.id}"
}