diff --git a/terraform/context_apply_test.go b/terraform/context_apply_test.go index 88ca29cc41a0..53292c89067f 100644 --- a/terraform/context_apply_test.go +++ b/terraform/context_apply_test.go @@ -10526,7 +10526,6 @@ func TestContext2Apply_plannedDestroyInterpolatedCount(t *testing.T) { } ctxOpts.ProviderResolver = providerResolver - ctxOpts.Destroy = true ctx, diags = NewContext(ctxOpts) if diags.HasErrors() { t.Fatalf("err: %s", diags.Err()) diff --git a/terraform/graph_builder_apply.go b/terraform/graph_builder_apply.go index 6157313281c2..00ddcf5c4034 100644 --- a/terraform/graph_builder_apply.go +++ b/terraform/graph_builder_apply.go @@ -175,17 +175,20 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer { // Reverse the edges from outputs and locals, so that // interpolations don't fail during destroy. // Create a destroy node for outputs to remove them from the state. - // Prune unreferenced values, which may have interpolations that can't - // be resolved. GraphTransformIf( func() bool { return b.Destroy }, GraphTransformMulti( &DestroyValueReferenceTransformer{}, &DestroyOutputTransformer{}, - &PruneUnusedValuesTransformer{}, ), ), + // Prune unreferenced values, which may have interpolations that can't + // be resolved. + &PruneUnusedValuesTransformer{ + Destroy: b.Destroy, + }, + // Add the node to fix the state count boundaries &CountBoundaryTransformer{ Config: b.Config, diff --git a/terraform/terraform_test.go b/terraform/terraform_test.go index 8458819773ab..6eeff9715676 100644 --- a/terraform/terraform_test.go +++ b/terraform/terraform_test.go @@ -406,9 +406,7 @@ module.child: Outputs: -aws_access_key = YYYYY aws_route53_zone_id = XXXX -aws_secret_key = ZZZZ ` const testTerraformApplyDependsCreateBeforeStr = ` @@ -693,11 +691,6 @@ foo = bar const testTerraformApplyOutputOrphanModuleStr = ` -module.child: - - Outputs: - - foo = bar ` const testTerraformApplyProvisionerStr = ` diff --git a/terraform/testdata/plan-destroy-interpolated-count/main.tf b/terraform/testdata/plan-destroy-interpolated-count/main.tf index b4ef77aba778..ac0dadbf81f8 100644 --- a/terraform/testdata/plan-destroy-interpolated-count/main.tf +++ b/terraform/testdata/plan-destroy-interpolated-count/main.tf @@ -3,9 +3,18 @@ variable "list" { } resource "aws_instance" "a" { - count = "${length(var.list)}" + count = length(var.list) +} + +locals { + ids = aws_instance.a[*].id +} + +module "empty" { + source = "./mod" + input = zipmap(var.list, local.ids) } output "out" { - value = "${aws_instance.a.*.id}" + value = aws_instance.a[*].id } diff --git a/terraform/testdata/plan-destroy-interpolated-count/mod/main.tf b/terraform/testdata/plan-destroy-interpolated-count/mod/main.tf new file mode 100644 index 000000000000..682e0f0db76a --- /dev/null +++ b/terraform/testdata/plan-destroy-interpolated-count/mod/main.tf @@ -0,0 +1,2 @@ +variable "input" { +} diff --git a/terraform/transform_reference.go b/terraform/transform_reference.go index 25e544996ef2..df34a3cea8cc 100644 --- a/terraform/transform_reference.go +++ b/terraform/transform_reference.go @@ -206,21 +206,30 @@ func (t *DestroyValueReferenceTransformer) Transform(g *Graph) error { return nil } -// PruneUnusedValuesTransformer is s GraphTransformer that removes local and -// output values which are not referenced in the graph. Since outputs and -// locals always need to be evaluated, if they reference a resource that is not -// available in the state the interpolation could fail. -type PruneUnusedValuesTransformer struct{} +// PruneUnusedValuesTransformer is a GraphTransformer that removes local, +// variable, and output values which are not referenced in the graph. If these +// values reference a resource that is no longer in the state the interpolation +// could fail. +type PruneUnusedValuesTransformer struct { + Destroy bool +} func (t *PruneUnusedValuesTransformer) Transform(g *Graph) error { - // this might need multiple runs in order to ensure that pruning a value - // doesn't effect a previously checked value. + // Pruning a value can effect previously checked edges, so loop until there + // are no more changes. for removed := 0; ; removed = 0 { for _, v := range g.Vertices() { - switch v.(type) { - case *NodeApplyableOutput, *NodeLocal: + switch v := v.(type) { + case *NodeApplyableOutput: + // If we're not certain this is a full destroy, we need to keep any + // root module outputs + if v.Addr.Module.IsRoot() && !t.Destroy { + continue + } + case *NodeLocal, *NodeApplyableModuleVariable: // OK default: + // We're only concerned with variables, locals and outputs continue } @@ -229,6 +238,7 @@ func (t *PruneUnusedValuesTransformer) Transform(g *Graph) error { switch dependants.Len() { case 0: // nothing at all depends on this + log.Printf("[TRACE] PruneUnusedValuesTransformer: removing unused value %s", dag.VertexName(v)) g.Remove(v) removed++ case 1: @@ -236,6 +246,7 @@ func (t *PruneUnusedValuesTransformer) Transform(g *Graph) error { // we need to check for the case of a single destroy node. d := dependants.List()[0] if _, ok := d.(*NodeDestroyableOutput); ok { + log.Printf("[TRACE] PruneUnusedValuesTransformer: removing unused value %s", dag.VertexName(v)) g.Remove(v) removed++ }