From cfefeec9260146313833e867df18541c2bca0a78 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Tue, 10 Apr 2018 11:32:52 -0400 Subject: [PATCH] walkDestroy is a form of "apply" When computing the count value, make sure to include walkDestroy with walkApply, as the former is only a special case of the latter. When applying a saved plan, the computed count values are lost and we can no longer query the state for those values. The apply walk was already considered in the `resourceCountMax` function, but the destroy walk was not. This worked when destroying in a single operation ("terraform destroy"), since the state would still be updated with the latest counts from the plan. --- terraform/context_apply_test.go | 83 +++++++++++++++++++ terraform/interpolate.go | 3 +- .../plan-destroy-interpolated-count/main.tf | 11 +++ 3 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 terraform/test-fixtures/plan-destroy-interpolated-count/main.tf diff --git a/terraform/context_apply_test.go b/terraform/context_apply_test.go index 8da5b720e..9bea9b1ab 100644 --- a/terraform/context_apply_test.go +++ b/terraform/context_apply_test.go @@ -9578,6 +9578,89 @@ func TestContext2Apply_plannedInterpolatedCount(t *testing.T) { } } +func TestContext2Apply_plannedDestroyInterpolatedCount(t *testing.T) { + m := testModule(t, "plan-destroy-interpolated-count") + + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + + providerResolver := ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ) + + s := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.a.0": { + Type: "aws_instance", + Primary: &InstanceState{ + ID: "foo", + }, + Provider: "provider.aws", + }, + "aws_instance.a.1": { + Type: "aws_instance", + Primary: &InstanceState{ + ID: "foo", + }, + Provider: "provider.aws", + }, + }, + Outputs: map[string]*OutputState{ + "out": { + Type: "list", + Value: []string{"foo", "foo"}, + }, + }, + }, + }, + } + + ctx := testContext2(t, &ContextOpts{ + Module: m, + ProviderResolver: providerResolver, + State: s, + Destroy: true, + }) + + plan, err := ctx.Plan() + if err != nil { + t.Fatalf("plan failed: %s", err) + } + + // We'll marshal and unmarshal the plan here, to ensure that we have + // a clean new context as would be created if we separately ran + // terraform plan -out=tfplan && terraform apply tfplan + var planBuf bytes.Buffer + err = WritePlan(plan, &planBuf) + if err != nil { + t.Fatalf("failed to write plan: %s", err) + } + plan, err = ReadPlan(&planBuf) + if err != nil { + t.Fatalf("failed to read plan: %s", err) + } + + ctx, err = plan.Context(&ContextOpts{ + ProviderResolver: providerResolver, + Destroy: true, + }) + if err != nil { + t.Fatalf("failed to create context for plan: %s", err) + } + + // Applying the plan should now succeed + _, err = ctx.Apply() + if err != nil { + t.Fatalf("apply failed: %s", err) + } +} + func TestContext2Apply_scaleInMultivarRef(t *testing.T) { m := testModule(t, "apply-resource-scale-in") diff --git a/terraform/interpolate.go b/terraform/interpolate.go index f3d9adefe..4f4e178cf 100644 --- a/terraform/interpolate.go +++ b/terraform/interpolate.go @@ -789,7 +789,8 @@ func (i *Interpolater) resourceCountMax( // If we're NOT applying, then we assume we can read the count // from the state. Plan and so on may not have any state yet so // we do a full interpolation. - if i.Operation != walkApply { + // Don't forget walkDestroy, which is a special case of walkApply + if !(i.Operation == walkApply || i.Operation == walkDestroy) { if cr == nil { return 0, nil } diff --git a/terraform/test-fixtures/plan-destroy-interpolated-count/main.tf b/terraform/test-fixtures/plan-destroy-interpolated-count/main.tf new file mode 100644 index 000000000..b4ef77aba --- /dev/null +++ b/terraform/test-fixtures/plan-destroy-interpolated-count/main.tf @@ -0,0 +1,11 @@ +variable "list" { + default = ["1", "2"] +} + +resource "aws_instance" "a" { + count = "${length(var.list)}" +} + +output "out" { + value = "${aws_instance.a.*.id}" +}