diff --git a/terraform/context.go b/terraform/context.go index 58cf0542b..95102cc80 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -498,7 +498,7 @@ func (c *walkContext) Walk() error { outputs := make(map[string]string) for _, o := range conf.Outputs { - if err := c.computeVars(o.RawConfig); err != nil { + if err := c.computeVars(o.RawConfig, nil); err != nil { return err } vraw := o.RawConfig.Config()["value"] @@ -619,7 +619,7 @@ func (c *walkContext) applyWalkFn() depgraph.WalkFunc { if !diff.Destroy { // Since we need the configuration, interpolate the variables - if err := r.Config.interpolate(c); err != nil { + if err := r.Config.interpolate(c, r); err != nil { return err } @@ -780,7 +780,7 @@ func (c *walkContext) planWalkFn() depgraph.WalkFunc { diff = &InstanceDiff{Destroy: true} } else { // Make sure the configuration is interpolated - if err := r.Config.interpolate(c); err != nil { + if err := r.Config.interpolate(c, r); err != nil { return err } @@ -993,7 +993,7 @@ func (c *walkContext) validateWalkFn() depgraph.WalkFunc { if rn.ExpandMode > ResourceExpandNone { // Interpolate the count and verify it is non-negative rc := NewResourceConfig(rn.Config.RawCount) - rc.interpolate(c) + rc.interpolate(c, rn.Resource) count, err := rn.Config.Count() if err == nil { if count < 0 { @@ -1063,7 +1063,7 @@ func (c *walkContext) validateWalkFn() depgraph.WalkFunc { for k, p := range sharedProvider.Providers { // Merge the configurations to get what we use to configure with rc := sharedProvider.MergeConfig(false, cs[k]) - rc.interpolate(c) + rc.interpolate(c, nil) log.Printf("[INFO] Validating provider: %s", k) ws, es := p.Validate(rc) @@ -1125,7 +1125,7 @@ func (c *walkContext) genericWalkFn(cb genericWalkFunc) depgraph.WalkFunc { wc.Variables = make(map[string]string) rc := NewResourceConfig(m.Config.RawConfig) - rc.interpolate(c) + rc.interpolate(c, nil) for k, v := range rc.Config { wc.Variables[k] = v.(string) } @@ -1151,7 +1151,7 @@ func (c *walkContext) genericWalkFn(cb genericWalkFunc) depgraph.WalkFunc { for k, p := range sharedProvider.Providers { // Merge the configurations to get what we use to configure with rc := sharedProvider.MergeConfig(false, cs[k]) - rc.interpolate(c) + rc.interpolate(c, nil) log.Printf("[INFO] Configuring provider: %s", k) err := p.Configure(rc) @@ -1211,7 +1211,7 @@ func (c *walkContext) genericWalkResource( rn *GraphNodeResource, fn depgraph.WalkFunc) error { // Interpolate the count rc := NewResourceConfig(rn.Config.RawCount) - rc.interpolate(c) + rc.interpolate(c, rn.Resource) // Expand the node to the actual resources ns, err := rn.Expand() @@ -1260,13 +1260,13 @@ func (c *walkContext) applyProvisioners(r *Resource, is *InstanceState) error { for _, prov := range r.Provisioners { // Interpolate since we may have variables that depend on the // local resource. - if err := prov.Config.interpolate(c); err != nil { + if err := prov.Config.interpolate(c, r); err != nil { return err } // Interpolate the conn info, since it may contain variables connInfo := NewResourceConfig(prov.ConnInfo) - if err := connInfo.interpolate(c); err != nil { + if err := connInfo.interpolate(c, r); err != nil { return err } @@ -1396,7 +1396,8 @@ func (c *walkContext) persistState(r *Resource) { // computeVars takes the State and given RawConfig and processes all // the variables. This dynamically discovers the attributes instead of // using a static map[string]string that the genericWalkFn uses. -func (c *walkContext) computeVars(raw *config.RawConfig) error { +func (c *walkContext) computeVars( + raw *config.RawConfig, r *Resource) error { // If there isn't a raw configuration, don't do anything if raw == nil { return nil @@ -1411,6 +1412,11 @@ func (c *walkContext) computeVars(raw *config.RawConfig) error { // Next, the actual computed variables for n, rawV := range raw.Variables { switch v := rawV.(type) { + case *config.CountVariable: + switch v.Type { + case config.CountValueIndex: + vs[n] = strconv.FormatInt(int64(r.CountIndex), 10) + } case *config.ModuleVariable: value, err := c.computeModuleVariable(v) if err != nil { diff --git a/terraform/context_test.go b/terraform/context_test.go index 29c927838..22a580ca2 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -2464,6 +2464,29 @@ func TestContextPlan_countComputed(t *testing.T) { } } +func TestContextPlan_countIndex(t *testing.T) { + m := testModule(t, "plan-count-index") + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + plan, err := ctx.Plan(nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(testTerraformPlanCountIndexStr) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + func TestContextPlan_countVar(t *testing.T) { m := testModule(t, "plan-count-var") p := testProvider("aws") diff --git a/terraform/graph.go b/terraform/graph.go index 49cf68edb..19b026715 100644 --- a/terraform/graph.go +++ b/terraform/graph.go @@ -1704,6 +1704,7 @@ func (n *GraphNodeResource) expand(g *depgraph.Graph, count int) { // Copy the base resource so we can fill it in resource := n.copyResource(name) + resource.CountIndex = i resource.State = state.Primary resource.Flags = flags diff --git a/terraform/resource.go b/terraform/resource.go index f76cfcba9..b43d296ac 100644 --- a/terraform/resource.go +++ b/terraform/resource.go @@ -34,6 +34,7 @@ type Resource struct { Provider ResourceProvider State *InstanceState Provisioners []*ResourceProvisionerConfig + CountIndex int Flags ResourceFlag TaintedIndex int } @@ -92,7 +93,7 @@ type ResourceConfig struct { // NewResourceConfig creates a new ResourceConfig from a config.RawConfig. func NewResourceConfig(c *config.RawConfig) *ResourceConfig { result := &ResourceConfig{raw: c} - result.interpolate(nil) + result.interpolate(nil, nil) return result } @@ -190,13 +191,14 @@ func (c *ResourceConfig) get( return current, true } -func (c *ResourceConfig) interpolate(ctx *walkContext) error { +func (c *ResourceConfig) interpolate( + ctx *walkContext, r *Resource) error { if c == nil { return nil } if ctx != nil { - if err := ctx.computeVars(c.raw); err != nil { + if err := ctx.computeVars(c.raw, r); err != nil { return err } } diff --git a/terraform/resource_test.go b/terraform/resource_test.go index ff85ab1b4..cc260ec3f 100644 --- a/terraform/resource_test.go +++ b/terraform/resource_test.go @@ -102,7 +102,10 @@ func TestResourceConfigGet(t *testing.T) { rc := NewResourceConfig(rawC) if tc.Vars != nil { ctx := NewContext(&ContextOpts{Variables: tc.Vars}) - if err := rc.interpolate(ctx.walkContext(walkInvalid, rootModulePath)); err != nil { + err := rc.interpolate( + ctx.walkContext(walkInvalid, rootModulePath), + nil) + if err != nil { t.Fatalf("err: %s", err) } } diff --git a/terraform/terraform_test.go b/terraform/terraform_test.go index 8041b139e..eb70418d5 100644 --- a/terraform/terraform_test.go +++ b/terraform/terraform_test.go @@ -477,6 +477,21 @@ STATE: ` +const testTerraformPlanCountIndexStr = ` +DIFF: + +CREATE: aws_instance.foo.0 + foo: "" => "0" + type: "" => "aws_instance" +CREATE: aws_instance.foo.1 + foo: "" => "1" + type: "" => "aws_instance" + +STATE: + + +` + const testTerraformPlanCountOneIndexStr = ` DIFF: diff --git a/terraform/test-fixtures/plan-count-index/main.tf b/terraform/test-fixtures/plan-count-index/main.tf new file mode 100644 index 000000000..9a0d1ebbc --- /dev/null +++ b/terraform/test-fixtures/plan-count-index/main.tf @@ -0,0 +1,4 @@ +resource "aws_instance" "foo" { + count = 2 + foo = "${count.index}" +}