diff --git a/config/config.go b/config/config.go index 5814141f8..ed4aaaf88 100644 --- a/config/config.go +++ b/config/config.go @@ -290,6 +290,14 @@ func (c *Config) Validate() error { raw[k] = strVal } + // Check for invalid count variables + for _, v := range m.RawConfig.Variables { + if _, ok := v.(*CountVariable); ok { + errs = append(errs, fmt.Errorf( + "%s: count variables are only valid within resources", m.Name)) + } + } + // Update the raw configuration to only contain the string values m.RawConfig, err = NewRawConfig(raw) if err != nil { @@ -472,6 +480,13 @@ func (c *Config) Validate() error { errs = append(errs, fmt.Errorf( "%s: output should only have 'value' field", o.Name)) } + + for _, v := range o.RawConfig.Variables { + if _, ok := v.(*CountVariable); ok { + errs = append(errs, fmt.Errorf( + "%s: count variables are only valid within resources", o.Name)) + } + } } // Check that all variables are in the proper context diff --git a/config/config_test.go b/config/config_test.go index 0503d2e66..3f67bdbf6 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -3,6 +3,7 @@ package config import ( "path/filepath" "reflect" + "strings" "testing" ) @@ -60,6 +61,22 @@ func TestConfigValidate_countInt(t *testing.T) { } } +func TestConfigValidate_countBadContext(t *testing.T) { + c := testConfig(t, "validate-count-bad-context") + + err := c.Validate() + + expected := []string{ + "no_count_in_output: count variables are only valid within resources", + "no_count_in_module: count variables are only valid within resources", + } + for _, exp := range expected { + if !strings.Contains(err.Error(), exp) { + t.Fatalf("expected: %q,\nto contain: %q", err, exp) + } + } +} + func TestConfigValidate_countCountVar(t *testing.T) { c := testConfig(t, "validate-count-count-var") if err := c.Validate(); err == nil { diff --git a/config/test-fixtures/validate-count-bad-context/main.tf b/config/test-fixtures/validate-count-bad-context/main.tf new file mode 100644 index 000000000..2d3833b65 --- /dev/null +++ b/config/test-fixtures/validate-count-bad-context/main.tf @@ -0,0 +1,11 @@ +resource "aws_instance" "foo" { +} + +output "no_count_in_output" { + value = "${count.index}" +} + +module "no_count_in_module" { + source = "./child" + somevar = "${count.index}" +} diff --git a/terraform/interpolate.go b/terraform/interpolate.go index a1e6d37af..aee283a78 100644 --- a/terraform/interpolate.go +++ b/terraform/interpolate.go @@ -85,6 +85,9 @@ func (i *Interpolater) valueCountVar( result map[string]ast.Variable) error { switch v.Type { case config.CountValueIndex: + if scope.Resource == nil { + return fmt.Errorf("%s: count.index is only valid within resources", n) + } result[n] = ast.Variable{ Value: scope.Resource.CountIndex, Type: ast.TypeInt, diff --git a/terraform/interpolate_test.go b/terraform/interpolate_test.go index 13d56fffb..52896a54b 100644 --- a/terraform/interpolate_test.go +++ b/terraform/interpolate_test.go @@ -1,6 +1,7 @@ package terraform import ( + "fmt" "os" "reflect" "sync" @@ -24,6 +25,31 @@ func TestInterpolater_countIndex(t *testing.T) { }) } +func TestInterpolater_countIndexInWrongContext(t *testing.T) { + i := &Interpolater{} + + scope := &InterpolationScope{ + Path: rootModulePath, + } + + n := "count.index" + + v, err := config.NewInterpolatedVariable(n) + if err != nil { + t.Fatalf("err: %s", err) + } + + expectedErr := fmt.Errorf("foo: count.index is only valid within resources") + + _, err = i.Values(scope, map[string]config.InterpolatedVariable{ + "foo": v, + }) + + if !reflect.DeepEqual(expectedErr, err) { + t.Fatalf("expected: %#v, got %#v", expectedErr, err) + } +} + func TestInterpolater_moduleVariable(t *testing.T) { lock := new(sync.RWMutex) state := &State{