terraform: remove so much destroy transform
This commit is contained in:
parent
b1b0e30f5a
commit
be56ebf770
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
`
|
Loading…
Reference in New Issue