terraform: put destroy nodes into the graph

This commit is contained in:
Mitchell Hashimoto 2015-02-12 10:54:28 -08:00
parent 4caab6870b
commit d7dc0291f5
6 changed files with 126 additions and 0 deletions

View File

@ -95,6 +95,9 @@ func (b *BuiltinGraphBuilder) Steps() []GraphTransformer {
},
},
// Create the destruction nodes
&DestroyTransformer{},
// Make sure we create one root
&RootTransformer{},
}

View File

@ -106,8 +106,12 @@ const testBasicGraphBuilderStr = `
const testBuiltinGraphBuilderBasicStr = `
aws_instance.db
aws_instance.db (destroy)
aws_instance.db (destroy)
provider.aws
aws_instance.web
aws_instance.web (destroy)
aws_instance.web (destroy)
aws_instance.db
provider.aws
provider.aws
@ -115,10 +119,14 @@ provider.aws
const testBuiltinGraphBuilderModuleStr = `
aws_instance.web
aws_instance.web (destroy)
aws_instance.web (destroy)
aws_security_group.firewall
module.consul (expanded)
provider.aws
aws_security_group.firewall
aws_security_group.firewall (destroy)
aws_security_group.firewall (destroy)
provider.aws
module.consul (expanded)
aws_security_group.firewall

View File

@ -243,6 +243,21 @@ func (n *GraphNodeConfigResource) ProvisionedBy() []string {
return result
}
// GraphNodeDestroyable
func (n *GraphNodeConfigResource) DestroyNode() dag.Vertex {
return &GraphNodeConfigResourceDestroy{Resource: n.Resource}
}
// GraphNodeConfigResourceDestroy represents the logical destroy step for
// a resource.
type GraphNodeConfigResourceDestroy struct {
Resource *config.Resource
}
func (n *GraphNodeConfigResourceDestroy) Name() string {
return fmt.Sprintf("%s (destroy)", n.Resource.Id())
}
// graphNodeModuleExpanded represents a module where the graph has
// been expanded. It stores the graph of the module as well as a reference
// to the map of variables.

View File

@ -0,0 +1,5 @@
resource "aws_instance" "foo" {}
resource "aws_instance" "bar" {
value = "${aws_instance.foo.value}"
}

View File

@ -0,0 +1,54 @@
package terraform
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. This vertex
// should not be in the graph yet.
DestroyNode() dag.Vertex
}
// DestroyTransformer is a GraphTransformer that creates the destruction
// nodes for things that _might_ be destroyed.
type DestroyTransformer struct{}
func (t *DestroyTransformer) Transform(g *Graph) error {
for _, v := range g.Vertices() {
// If it is not a destroyable, we don't care
dn, ok := v.(GraphNodeDestroyable)
if !ok {
continue
}
// Grab the destroy side of the node and connect it through
n := dn.DestroyNode()
if n == nil {
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 {
g.Connect(dag.BasicEdge(n, edgeRaw.(dag.Vertex)))
}
// Remove all the edges from the old now
for _, edgeRaw := range downEdges {
g.RemoveEdge(dag.BasicEdge(v, edgeRaw.(dag.Vertex)))
}
// Add a new edge to connect the node to be created to
// the destroy node.
g.Connect(dag.BasicEdge(v, n))
}
return nil
}

View File

@ -0,0 +1,41 @@
package terraform
import (
"strings"
"testing"
)
func TestDestroyTransformer(t *testing.T) {
mod := testModule(t, "transform-destroy-basic")
g := Graph{Path: RootModulePath}
{
tf := &ConfigTransformer{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)
}
}
const testTransformDestroyBasicStr = `
aws_instance.bar
aws_instance.bar (destroy)
aws_instance.bar (destroy)
aws_instance.foo
aws_instance.foo
aws_instance.foo (destroy)
aws_instance.foo (destroy)
`