From f3d1fb3aff94fc1595e6111cfc778eccc31cba69 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Fri, 9 Mar 2018 18:38:41 -0500 Subject: [PATCH 1/2] failing test for interpolated count from plan An interpolated count value that is determined during plan, is lost during plan serialization, causing apply to fail when the interpolation string can't be evaluated. --- terraform/context_apply_test.go | 67 +++++++++++++++++++ .../apply-interpolated-count/main.tf | 11 +++ 2 files changed, 78 insertions(+) create mode 100644 terraform/test-fixtures/apply-interpolated-count/main.tf diff --git a/terraform/context_apply_test.go b/terraform/context_apply_test.go index 28e42b02e893..69da40cfcb99 100644 --- a/terraform/context_apply_test.go +++ b/terraform/context_apply_test.go @@ -9472,5 +9472,72 @@ func TestContext2Apply_providersFromState(t *testing.T) { }) } +} + +func TestContext2Apply_plannedInterpolatedCount(t *testing.T) { + m := testModule(t, "apply-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.test": { + Type: "aws_instance", + Primary: &InstanceState{ + ID: "foo", + }, + Provider: "provider.aws", + }, + }, + }, + }, + } + + ctx := testContext2(t, &ContextOpts{ + Module: m, + ProviderResolver: providerResolver, + State: s, + }) + + 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, + }) + 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) + } } diff --git a/terraform/test-fixtures/apply-interpolated-count/main.tf b/terraform/test-fixtures/apply-interpolated-count/main.tf new file mode 100644 index 000000000000..c4ed06dc7049 --- /dev/null +++ b/terraform/test-fixtures/apply-interpolated-count/main.tf @@ -0,0 +1,11 @@ +variable "instance_count" { + default = 1 +} + +resource "aws_instance" "test" { + count = "${var.instance_count}" +} + +resource "aws_instance" "dependent" { + count = "${aws_instance.test.count}" +} From a2718e4f790d48f57c9769648cd6b3da435355f7 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Fri, 9 Mar 2018 19:07:28 -0500 Subject: [PATCH 2/2] ignore errors interpolating RawCount during apply If a count field references another count field which is interpolated but is attached to a resource already in the state, the result of that first interpolation will be lost when a plan is serialized. This is because the result of the first interpolation is stored directly in the module config, in an unexported config field. This is not a general fix for the above situation, which would require refactoring how counts are handles throughout the config. Ignoring the error works, because in most cases the count will be properly handled during the resource's interpolation. --- terraform/node_resource_apply.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/terraform/node_resource_apply.go b/terraform/node_resource_apply.go index 90448065596b..40ee1cf2a787 100644 --- a/terraform/node_resource_apply.go +++ b/terraform/node_resource_apply.go @@ -140,7 +140,10 @@ func (n *NodeApplyableResource) evalTreeDataResource( // Here we are just populating the interpolated value in-place // inside this RawConfig object, like we would in // NodeAbstractCountResource. - &EvalInterpolate{Config: n.Config.RawCount}, + &EvalInterpolate{ + Config: n.Config.RawCount, + ContinueOnErr: true, + }, // We need to re-interpolate the config here, rather than // just using the diff's values directly, because we've @@ -271,7 +274,10 @@ func (n *NodeApplyableResource) evalTreeManagedResource( // Here we are just populating the interpolated value in-place // inside this RawConfig object, like we would in // NodeAbstractCountResource. - &EvalInterpolate{Config: n.Config.RawCount}, + &EvalInterpolate{ + Config: n.Config.RawCount, + ContinueOnErr: true, + }, &EvalInterpolate{ Config: n.Config.RawConfig.Copy(),