diff --git a/terraform/context_apply_test.go b/terraform/context_apply_test.go index 61a9ea81a..95ac647b2 100644 --- a/terraform/context_apply_test.go +++ b/terraform/context_apply_test.go @@ -7641,6 +7641,8 @@ func TestContext2Apply_destroyProvisionerWithLocals(t *testing.T) { } } +// this also tests a local value in the config referencing a resource that +// wasn't in the state during destroy. func TestContext2Apply_destroyProvisionerWithMultipleLocals(t *testing.T) { m := testModule(t, "apply-provisioner-destroy-multiple-locals") p := testProvider("aws") diff --git a/terraform/graph_builder_apply.go b/terraform/graph_builder_apply.go index 62dc2d275..0c2b2332f 100644 --- a/terraform/graph_builder_apply.go +++ b/terraform/graph_builder_apply.go @@ -119,16 +119,19 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer { // Connect references so ordering is correct &ReferenceTransformer{}, + // Handle destroy time transformations for output and local values. // 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 }, - &DestroyValueReferenceTransformer{}, - ), - - GraphTransformIf( - func() bool { return b.Destroy }, - &DestroyOutputTransformer{}, + GraphTransformMulti( + &DestroyValueReferenceTransformer{}, + &DestroyOutputTransformer{}, + &PruneUnusedValuesTransformer{}, + ), ), // Add the node to fix the state count boundaries diff --git a/terraform/transform_reference.go b/terraform/transform_reference.go index fa4f99989..403b7e424 100644 --- a/terraform/transform_reference.go +++ b/terraform/transform_reference.go @@ -112,6 +112,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{} + +func (t *PruneUnusedValuesTransformer) Transform(g *Graph) error { + vs := g.Vertices() + for _, v := range vs { + switch v.(type) { + case *NodeApplyableOutput, *NodeLocal: + // OK + default: + continue + } + + if len(g.EdgesTo(v)) == 0 { + g.Remove(v) + } + } + + return nil +} + // ReferenceMap is a structure that can be used to efficiently check // for references on a graph. type ReferenceMap struct {