config: validate that multi-variables are only used in slices
This commit is contained in:
parent
75e79da9c3
commit
7b48924532
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -22,8 +22,17 @@ 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 interpolationWalkerFunc
|
||||
Replace bool
|
||||
// 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
|
||||
|
@ -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(
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
resource "aws_instance" "foo" {
|
||||
count = 3
|
||||
}
|
||||
|
||||
resource "aws_instance" "bar" {
|
||||
foo = "${aws_instance.foo.*.id}"
|
||||
}
|
Loading…
Reference in New Issue