diff --git a/terraform/transform_destroy.go b/terraform/transform_destroy.go index b529daf95..45eaae133 100644 --- a/terraform/transform_destroy.go +++ b/terraform/transform_destroy.go @@ -4,16 +4,6 @@ import ( "github.com/hashicorp/terraform/dag" ) -// GraphNodeDestroyable is the interface that nodes that can be destroyed -// must implement. This is used to automatically handle the creation of -// destroy nodes in the graph and the dependency ordering of those destroys. -type GraphNodeDestroyable interface { - // DestroyNode returns the node used for the destroy with the given - // mode. If this returns nil, then a destroy node for that mode - // will not be added. - DestroyNode() GraphNodeDestroy -} - // GraphNodeDestroy is the interface that must implemented by // nodes that destroy. type GraphNodeDestroy interface { @@ -28,168 +18,3 @@ type GraphNodeDestroy interface { // destroy. This must already exist within the graph. CreateNode() dag.Vertex } - -// GraphNodeDestroyPrunable is the interface that can be implemented to -// signal that this node can be pruned depending on state. -type GraphNodeDestroyPrunable interface { - // DestroyInclude is called to check if this node should be included - // with the given state. The state and diff must NOT be modified. - DestroyInclude(*ModuleDiff, *ModuleState) bool -} - -// GraphNodeEdgeInclude can be implemented to not include something -// as an edge within the destroy graph. This is usually done because it -// might cause unnecessary cycles. -type GraphNodeDestroyEdgeInclude interface { - DestroyEdgeInclude(dag.Vertex) bool -} - -// DestroyTransformer is a GraphTransformer that creates the destruction -// nodes for things that _might_ be destroyed. -type DestroyTransformer struct { - FullDestroy bool -} - -func (t *DestroyTransformer) Transform(g *Graph) error { - var connect, remove []dag.Edge - nodeToCn := make(map[dag.Vertex]dag.Vertex, len(g.Vertices())) - nodeToDn := make(map[dag.Vertex]dag.Vertex, len(g.Vertices())) - for _, v := range g.Vertices() { - // If it is not a destroyable, we don't care - cn, ok := v.(GraphNodeDestroyable) - if !ok { - continue - } - - // Grab the destroy side of the node and connect it through - n := cn.DestroyNode() - if n == nil { - continue - } - - // Store it - nodeToCn[n] = cn - nodeToDn[cn] = n - - // If the creation node is equal to the destroy node, then - // don't do any of the edge jump rope below. - if n.(interface{}) == cn.(interface{}) { - continue - } - - // Add it to the graph - g.Add(n) - - // Inherit all the edges from the old node - downEdges := g.DownEdges(v).List() - for _, edgeRaw := range downEdges { - // If this thing specifically requests to not be depended on - // by destroy nodes, then don't. - if i, ok := edgeRaw.(GraphNodeDestroyEdgeInclude); ok && - !i.DestroyEdgeInclude(v) { - continue - } - - g.Connect(dag.BasicEdge(n, edgeRaw.(dag.Vertex))) - } - - // Add a new edge to connect the node to be created to - // the destroy node. - connect = append(connect, dag.BasicEdge(v, n)) - } - - // Go through the nodes we added and determine if they depend - // on any nodes with a destroy node. If so, depend on that instead. - for n, _ := range nodeToCn { - for _, downRaw := range g.DownEdges(n).List() { - target := downRaw.(dag.Vertex) - cn2, ok := target.(GraphNodeDestroyable) - if !ok { - continue - } - - newTarget := nodeToDn[cn2] - if newTarget == nil { - continue - } - - // Make the new edge and transpose - connect = append(connect, dag.BasicEdge(newTarget, n)) - - // Remove the old edge - remove = append(remove, dag.BasicEdge(n, target)) - } - } - - // Atomatically add/remove the edges - for _, e := range connect { - g.Connect(e) - } - for _, e := range remove { - g.RemoveEdge(e) - } - - return nil -} - -// noCreateBeforeDestroyAncestors verifies that a vertex has no ancestors that -// are CreateBeforeDestroy. -// If this vertex has an ancestor with CreateBeforeDestroy, we will need to -// inherit that behavior and re-order the edges even if this node type doesn't -// directly implement CreateBeforeDestroy. -func noCreateBeforeDestroyAncestors(g *Graph, v dag.Vertex) bool { - s, _ := g.Ancestors(v) - if s == nil { - return true - } - for _, v := range s.List() { - dn, ok := v.(GraphNodeDestroy) - if !ok { - continue - } - - if dn.CreateBeforeDestroy() { - // some ancestor is CreateBeforeDestroy, so we need to follow suit - return false - } - } - return true -} - -// PruneDestroyTransformer is a GraphTransformer that removes the destroy -// nodes that aren't in the diff. -type PruneDestroyTransformer struct { - Diff *Diff - State *State -} - -func (t *PruneDestroyTransformer) Transform(g *Graph) error { - for _, v := range g.Vertices() { - // If it is not a destroyer, we don't care - dn, ok := v.(GraphNodeDestroyPrunable) - if !ok { - continue - } - - path := g.Path - if pn, ok := v.(GraphNodeSubPath); ok { - path = pn.Path() - } - - var modDiff *ModuleDiff - var modState *ModuleState - if t.Diff != nil { - modDiff = t.Diff.ModuleByPath(path) - } - if t.State != nil { - modState = t.State.ModuleByPath(path) - } - - // Remove it if we should - if !dn.DestroyInclude(modDiff, modState) { - g.Remove(v) - } - } - - return nil -} diff --git a/terraform/transform_destroy_test.go b/terraform/transform_destroy_test.go deleted file mode 100644 index ae4d7b5e1..000000000 --- a/terraform/transform_destroy_test.go +++ /dev/null @@ -1,442 +0,0 @@ -package terraform - -import ( - "strings" - "testing" -) - -func TestDestroyTransformer(t *testing.T) { - mod := testModule(t, "transform-destroy-basic") - - g := Graph{Path: RootModulePath} - { - tf := &ConfigTransformerOld{Module: mod} - if err := tf.Transform(&g); err != nil { - t.Fatalf("err: %s", err) - } - } - - { - tf := &DestroyTransformer{} - if err := tf.Transform(&g); err != nil { - t.Fatalf("err: %s", err) - } - } - - actual := strings.TrimSpace(g.String()) - expected := strings.TrimSpace(testTransformDestroyBasicStr) - if actual != expected { - t.Fatalf("bad:\n\n%s", actual) - } -} - -func TestDestroyTransformer_dependsOn(t *testing.T) { - mod := testModule(t, "transform-destroy-depends-on") - - g := Graph{Path: RootModulePath} - { - tf := &ConfigTransformerOld{Module: mod} - if err := tf.Transform(&g); err != nil { - t.Fatalf("err: %s", err) - } - } - - { - tf := &DestroyTransformer{} - if err := tf.Transform(&g); err != nil { - t.Fatalf("err: %s", err) - } - } - - actual := strings.TrimSpace(g.String()) - expected := strings.TrimSpace(testTransformDestroyBasicStr) - if actual != expected { - t.Fatalf("bad:\n\n%s", actual) - } -} - -func TestPruneDestroyTransformer(t *testing.T) { - var diff *Diff - mod := testModule(t, "transform-destroy-basic") - - g := Graph{Path: RootModulePath} - { - tf := &ConfigTransformerOld{Module: mod} - if err := tf.Transform(&g); err != nil { - t.Fatalf("err: %s", err) - } - } - - { - tf := &DestroyTransformer{} - if err := tf.Transform(&g); err != nil { - t.Fatalf("err: %s", err) - } - } - - { - tf := &PruneDestroyTransformer{Diff: diff} - if err := tf.Transform(&g); err != nil { - t.Fatalf("err: %s", err) - } - } - - actual := strings.TrimSpace(g.String()) - expected := strings.TrimSpace(testTransformPruneDestroyBasicStr) - if actual != expected { - t.Fatalf("bad:\n\n%s", actual) - } -} - -func TestPruneDestroyTransformer_diff(t *testing.T) { - mod := testModule(t, "transform-destroy-basic") - - diff := &Diff{ - Modules: []*ModuleDiff{ - &ModuleDiff{ - Path: RootModulePath, - Resources: map[string]*InstanceDiff{ - "aws_instance.bar": &InstanceDiff{}, - }, - }, - }, - } - - g := Graph{Path: RootModulePath} - { - tf := &ConfigTransformerOld{Module: mod} - if err := tf.Transform(&g); err != nil { - t.Fatalf("err: %s", err) - } - } - - { - tf := &DestroyTransformer{} - if err := tf.Transform(&g); err != nil { - t.Fatalf("err: %s", err) - } - } - - { - tf := &PruneDestroyTransformer{Diff: diff} - if err := tf.Transform(&g); err != nil { - t.Fatalf("err: %s", err) - } - } - - actual := strings.TrimSpace(g.String()) - expected := strings.TrimSpace(testTransformPruneDestroyBasicDiffStr) - if actual != expected { - t.Fatalf("expected:\n\n%s\n\nbad:\n\n%s", expected, actual) - } -} - -func TestPruneDestroyTransformer_count(t *testing.T) { - mod := testModule(t, "transform-destroy-prune-count") - - diff := &Diff{} - - g := Graph{Path: RootModulePath} - { - tf := &ConfigTransformerOld{Module: mod} - if err := tf.Transform(&g); err != nil { - t.Fatalf("err: %s", err) - } - } - - { - tf := &DestroyTransformer{} - if err := tf.Transform(&g); err != nil { - t.Fatalf("err: %s", err) - } - } - - { - tf := &PruneDestroyTransformer{Diff: diff} - if err := tf.Transform(&g); err != nil { - t.Fatalf("err: %s", err) - } - } - - actual := strings.TrimSpace(g.String()) - expected := strings.TrimSpace(testTransformPruneDestroyCountStr) - if actual != expected { - t.Fatalf("bad:\n\n%s", actual) - } -} - -func TestPruneDestroyTransformer_countDec(t *testing.T) { - mod := testModule(t, "transform-destroy-basic") - - diff := &Diff{} - state := &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: RootModulePath, - Resources: map[string]*ResourceState{ - "aws_instance.bar.1": &ResourceState{ - Primary: &InstanceState{}, - }, - "aws_instance.bar.2": &ResourceState{ - Primary: &InstanceState{}, - }, - }, - }, - }, - } - - g := Graph{Path: RootModulePath} - { - tf := &ConfigTransformerOld{Module: mod} - if err := tf.Transform(&g); err != nil { - t.Fatalf("err: %s", err) - } - } - - { - tf := &DestroyTransformer{} - if err := tf.Transform(&g); err != nil { - t.Fatalf("err: %s", err) - } - } - - { - tf := &PruneDestroyTransformer{Diff: diff, State: state} - if err := tf.Transform(&g); err != nil { - t.Fatalf("err: %s", err) - } - } - - actual := strings.TrimSpace(g.String()) - expected := strings.TrimSpace(testTransformPruneDestroyCountDecStr) - if actual != expected { - t.Fatalf("bad:\n\n%s", actual) - } -} - -func TestPruneDestroyTransformer_countState(t *testing.T) { - mod := testModule(t, "transform-destroy-basic") - - diff := &Diff{} - state := &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: RootModulePath, - Resources: map[string]*ResourceState{ - "aws_instance.bar": &ResourceState{ - Primary: &InstanceState{}, - }, - }, - }, - }, - } - - g := Graph{Path: RootModulePath} - { - tf := &ConfigTransformerOld{Module: mod} - if err := tf.Transform(&g); err != nil { - t.Fatalf("err: %s", err) - } - } - - { - tf := &DestroyTransformer{} - if err := tf.Transform(&g); err != nil { - t.Fatalf("err: %s", err) - } - } - - { - tf := &PruneDestroyTransformer{Diff: diff, State: state} - if err := tf.Transform(&g); err != nil { - t.Fatalf("err: %s", err) - } - } - - actual := strings.TrimSpace(g.String()) - expected := strings.TrimSpace(testTransformPruneDestroyCountStateStr) - if actual != expected { - t.Fatalf("bad:\n\n%s", actual) - } -} - -func TestPruneDestroyTransformer_prefixMatch(t *testing.T) { - mod := testModule(t, "transform-destroy-prefix") - - diff := &Diff{} - state := &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: RootModulePath, - Resources: map[string]*ResourceState{ - "aws_instance.foo-bar.0": &ResourceState{ - Primary: &InstanceState{ID: "foo"}, - }, - - "aws_instance.foo-bar.1": &ResourceState{ - Primary: &InstanceState{ID: "foo"}, - }, - }, - }, - }, - } - - g := Graph{Path: RootModulePath} - { - tf := &ConfigTransformerOld{Module: mod} - if err := tf.Transform(&g); err != nil { - t.Fatalf("err: %s", err) - } - } - - { - tf := &DestroyTransformer{} - if err := tf.Transform(&g); err != nil { - t.Fatalf("err: %s", err) - } - } - - { - tf := &PruneDestroyTransformer{Diff: diff, State: state} - if err := tf.Transform(&g); err != nil { - t.Fatalf("err: %s", err) - } - } - - actual := strings.TrimSpace(g.String()) - expected := strings.TrimSpace(testTransformPruneDestroyPrefixStr) - if actual != expected { - t.Fatalf("bad:\n\n%s", actual) - } -} - -func TestPruneDestroyTransformer_tainted(t *testing.T) { - mod := testModule(t, "transform-destroy-basic") - - diff := &Diff{} - state := &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: RootModulePath, - Resources: map[string]*ResourceState{ - "aws_instance.bar": &ResourceState{ - Primary: &InstanceState{ - ID: "foo", - Tainted: true, - }, - }, - }, - }, - }, - } - - g := Graph{Path: RootModulePath} - { - tf := &ConfigTransformerOld{Module: mod} - if err := tf.Transform(&g); err != nil { - t.Fatalf("err: %s", err) - } - } - - { - tf := &DestroyTransformer{} - if err := tf.Transform(&g); err != nil { - t.Fatalf("err: %s", err) - } - } - - { - tf := &PruneDestroyTransformer{Diff: diff, State: state} - if err := tf.Transform(&g); err != nil { - t.Fatalf("err: %s", err) - } - } - - actual := strings.TrimSpace(g.String()) - expected := strings.TrimSpace(testTransformPruneDestroyTaintedStr) - if actual != expected { - t.Fatalf("bad:\n\n%s", actual) - } -} - -const testTransformDestroyBasicStr = ` -aws_instance.bar - aws_instance.bar (destroy) - aws_instance.foo -aws_instance.bar (destroy) -aws_instance.foo - aws_instance.foo (destroy) -aws_instance.foo (destroy) - aws_instance.bar (destroy) -` - -const testTransformPruneDestroyBasicStr = ` -aws_instance.bar - aws_instance.foo -aws_instance.foo -` - -const testTransformPruneDestroyBasicDiffStr = ` -aws_instance.bar - aws_instance.foo -aws_instance.foo -` - -const testTransformPruneDestroyCountStr = ` -aws_instance.bar - aws_instance.bar (destroy) - aws_instance.foo -aws_instance.bar (destroy) -aws_instance.foo -` - -const testTransformPruneDestroyCountDecStr = ` -aws_instance.bar - aws_instance.bar (destroy) - aws_instance.foo -aws_instance.bar (destroy) -aws_instance.foo -` - -const testTransformPruneDestroyCountStateStr = ` -aws_instance.bar - aws_instance.foo -aws_instance.foo -` - -const testTransformPruneDestroyPrefixStr = ` -aws_instance.foo -aws_instance.foo-bar - aws_instance.foo-bar (destroy) -aws_instance.foo-bar (destroy) -` - -const testTransformPruneDestroyTaintedStr = ` -aws_instance.bar - aws_instance.foo -aws_instance.foo -` - -const testTransformCreateBeforeDestroyBasicStr = ` -aws_instance.web -aws_instance.web (destroy) - aws_instance.web - aws_load_balancer.lb - aws_load_balancer.lb (destroy) -aws_load_balancer.lb - aws_instance.web - aws_load_balancer.lb (destroy) -aws_load_balancer.lb (destroy) -` - -const testTransformCreateBeforeDestroyTwiceStr = ` -aws_autoscale.bar - aws_lc.foo -aws_autoscale.bar (destroy) - aws_autoscale.bar -aws_lc.foo -aws_lc.foo (destroy) - aws_autoscale.bar - aws_autoscale.bar (destroy) - aws_lc.foo -`