terraform: remove so much destroy transform

This commit is contained in:
Mitchell Hashimoto 2017-01-26 18:00:47 -08:00
parent b1b0e30f5a
commit be56ebf770
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
2 changed files with 0 additions and 617 deletions

View File

@ -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
}

View File

@ -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
`