terraform: test the destroy edge transform

This commit is contained in:
Mitchell Hashimoto 2016-09-20 10:16:49 -07:00
parent bd5d97f9f5
commit 7b2bd93094
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
4 changed files with 143 additions and 6 deletions

View File

@ -0,0 +1,2 @@
resource "test" "A" {}
resource "test" "B" { value = "${test.A.value}" }

View File

@ -29,6 +29,8 @@ type AttachResourceConfigTransformer struct {
} }
func (t *AttachResourceConfigTransformer) Transform(g *Graph) error { func (t *AttachResourceConfigTransformer) Transform(g *Graph) error {
log.Printf("[TRACE] AttachResourceConfigTransformer: Beginning...")
// Go through and find GraphNodeAttachResource // Go through and find GraphNodeAttachResource
for _, v := range g.Vertices() { for _, v := range g.Vertices() {
// Only care about GraphNodeAttachResource implementations // Only care about GraphNodeAttachResource implementations
@ -39,7 +41,7 @@ func (t *AttachResourceConfigTransformer) Transform(g *Graph) error {
// Determine what we're looking for // Determine what we're looking for
addr := arn.ResourceAddr() addr := arn.ResourceAddr()
log.Printf("[TRACE] Attach resource request: %s", addr) log.Printf("[TRACE] AttachResourceConfigTransformer: Attach resource request: %s", addr)
// Get the configuration. // Get the configuration.
path := normalizeModulePath(addr.Path) path := normalizeModulePath(addr.Path)

View File

@ -1,7 +1,16 @@
package terraform package terraform
import (
"log"
"github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/dag"
)
// GraphNodeDestroyer must be implemented by nodes that destroy resources. // GraphNodeDestroyer must be implemented by nodes that destroy resources.
type GraphNodeDestroyer interface { type GraphNodeDestroyer interface {
dag.Vertex
// ResourceAddr is the address of the resource that is being // ResourceAddr is the address of the resource that is being
// destroyed by this node. If this returns nil, then this node // destroyed by this node. If this returns nil, then this node
// is not destroying anything. // is not destroying anything.
@ -22,9 +31,16 @@ type GraphNodeDestroyer interface {
// dependent resources will block parent resources from deleting. Concrete // dependent resources will block parent resources from deleting. Concrete
// example: VPC with subnets, the VPC can't be deleted while there are // example: VPC with subnets, the VPC can't be deleted while there are
// still subnets. // still subnets.
type DestroyEdgeTransformer struct{} type DestroyEdgeTransformer struct {
// Module and State are only needed to look up dependencies in
// any way possible. Either can be nil if not availabile.
Module *module.Tree
State *State
}
func (t *DestroyEdgeTransformer) Transform(g *Graph) error { func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
log.Printf("[TRACE] DestroyEdgeTransformer: Beginning destroy edge transformation...")
// Build a map of what is being destroyed (by address string) to // Build a map of what is being destroyed (by address string) to
// the list of destroyers. In general there will only be one destroyer // the list of destroyers. In general there will only be one destroyer
// but to make it more robust we support multiple. // but to make it more robust we support multiple.
@ -41,6 +57,9 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
} }
key := addr.String() key := addr.String()
log.Printf(
"[TRACE] DestroyEdgeTransformer: %s destroying %q",
dag.VertexName(dn), key)
destroyers[key] = append(destroyers[key], dn) destroyers[key] = append(destroyers[key], dn)
} }
@ -50,13 +69,83 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
return nil return nil
} }
// This is strange but is the easiest way to get the dependencies
// of a node that is being destroyed. We use another graph to make sure
// the resource is in the graph and ask for references. We have to do this
// because the node that is being destroyed may NOT be in the graph.
//
// Example: resource A is force new, then destroy A AND create A are
// in the graph. BUT if resource A is just pure destroy, then only
// destroy A is in the graph, and create A is not.
steps := []GraphTransformer{
&AttachResourceConfigTransformer{Module: t.Module},
&AttachStateTransformer{State: t.State},
}
// Go through the all destroyers and find what they're destroying. // Go through the all destroyers and find what they're destroying.
// Use this to find the dependencies, look up if any of them are being // Use this to find the dependencies, look up if any of them are being
// destroyed, and to make the proper edge. // destroyed, and to make the proper edge.
for _, ds := range destroyers { for d, dns := range destroyers {
for _, d := range ds { // d is what is being destroyed. We parse the resource address
// TODO // which it came from it is a panic if this fails.
println(d) addr, err := ParseResourceAddress(d)
if err != nil {
panic(err)
}
// This part is a little bit weird but is the best way to
// find the dependencies we need to: build a graph and use the
// attach config and state transformers then ask for references.
node := &NodeApplyableResource{Addr: addr}
{
var g Graph
g.Add(node)
for _, s := range steps {
if err := s.Transform(&g); err != nil {
return err
}
}
}
// Get the references of the creation node. If it has none,
// then there are no edges to make here.
prefix := modulePrefixStr(normalizeModulePath(addr.Path))
deps := modulePrefixList(node.References(), prefix)
log.Printf(
"[TRACE] DestroyEdgeTransformer: creation of %q depends on %#v",
d, deps)
if len(deps) == 0 {
continue
}
// We have dependencies, check if any are being destroyed
// to build the list of things that we must depend on!
//
// In the example of the struct, if we have:
//
// B_d => A_d => A => B
//
// Then at this point in the algorithm we started with A_d,
// we built A (to get dependencies), and we found B. We're now looking
// to see if B_d exists.
var depDestroyers []dag.Vertex
for _, d := range deps {
if ds, ok := destroyers[d]; ok {
for _, d := range ds {
depDestroyers = append(depDestroyers, d.(dag.Vertex))
log.Printf(
"[TRACE] DestroyEdgeTransformer: destruction of %q depends on %s",
addr.String(), dag.VertexName(d))
}
}
}
// Go through and make the connections. Use the variable
// names "a_d" and "b_d" to reference our example.
for _, a_d := range dns {
for _, b_d := range depDestroyers {
g.Connect(dag.BasicEdge(b_d, a_d))
}
} }
} }

View File

@ -0,0 +1,44 @@
package terraform
import (
"strings"
"testing"
)
func TestDestroyEdgeTransformer(t *testing.T) {
g := Graph{Path: RootModulePath}
g.Add(&graphNodeDestroyerTest{AddrString: "test.A"})
g.Add(&graphNodeDestroyerTest{AddrString: "test.B"})
tf := &DestroyEdgeTransformer{
Module: testModule(t, "transform-destroy-edge-basic"),
}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformDestroyEdgeBasicStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
type graphNodeDestroyerTest struct {
AddrString string
}
func (n *graphNodeDestroyerTest) Name() string { return n.DestroyAddr().String() + " (destroy)" }
func (n *graphNodeDestroyerTest) DestroyAddr() *ResourceAddress {
addr, err := ParseResourceAddress(n.AddrString)
if err != nil {
panic(err)
}
return addr
}
const testTransformDestroyEdgeBasicStr = `
test.A (destroy)
test.B (destroy)
test.B (destroy)
`