From 7b2bd9309450e56db5c0b192b5cfafa82c7e3809 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 20 Sep 2016 10:16:49 -0700 Subject: [PATCH] terraform: test the destroy edge transform --- .../transform-destroy-edge-basic/main.tf | 2 + terraform/transform_attach_config_resource.go | 4 +- terraform/transform_destroy_edge.go | 99 ++++++++++++++++++- terraform/transform_destroy_edge_test.go | 44 +++++++++ 4 files changed, 143 insertions(+), 6 deletions(-) create mode 100644 terraform/test-fixtures/transform-destroy-edge-basic/main.tf create mode 100644 terraform/transform_destroy_edge_test.go diff --git a/terraform/test-fixtures/transform-destroy-edge-basic/main.tf b/terraform/test-fixtures/transform-destroy-edge-basic/main.tf new file mode 100644 index 000000000..a6a6a5ec4 --- /dev/null +++ b/terraform/test-fixtures/transform-destroy-edge-basic/main.tf @@ -0,0 +1,2 @@ +resource "test" "A" {} +resource "test" "B" { value = "${test.A.value}" } diff --git a/terraform/transform_attach_config_resource.go b/terraform/transform_attach_config_resource.go index 449d1ac16..83612fede 100644 --- a/terraform/transform_attach_config_resource.go +++ b/terraform/transform_attach_config_resource.go @@ -29,6 +29,8 @@ type AttachResourceConfigTransformer struct { } func (t *AttachResourceConfigTransformer) Transform(g *Graph) error { + log.Printf("[TRACE] AttachResourceConfigTransformer: Beginning...") + // Go through and find GraphNodeAttachResource for _, v := range g.Vertices() { // Only care about GraphNodeAttachResource implementations @@ -39,7 +41,7 @@ func (t *AttachResourceConfigTransformer) Transform(g *Graph) error { // Determine what we're looking for addr := arn.ResourceAddr() - log.Printf("[TRACE] Attach resource request: %s", addr) + log.Printf("[TRACE] AttachResourceConfigTransformer: Attach resource request: %s", addr) // Get the configuration. path := normalizeModulePath(addr.Path) diff --git a/terraform/transform_destroy_edge.go b/terraform/transform_destroy_edge.go index d1e9d194e..2b863bcc5 100644 --- a/terraform/transform_destroy_edge.go +++ b/terraform/transform_destroy_edge.go @@ -1,7 +1,16 @@ package terraform +import ( + "log" + + "github.com/hashicorp/terraform/config/module" + "github.com/hashicorp/terraform/dag" +) + // GraphNodeDestroyer must be implemented by nodes that destroy resources. type GraphNodeDestroyer interface { + dag.Vertex + // ResourceAddr is the address of the resource that is being // destroyed by this node. If this returns nil, then this node // is not destroying anything. @@ -22,9 +31,16 @@ type GraphNodeDestroyer interface { // dependent resources will block parent resources from deleting. Concrete // example: VPC with subnets, the VPC can't be deleted while there are // 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 { + log.Printf("[TRACE] DestroyEdgeTransformer: Beginning destroy edge transformation...") + // Build a map of what is being destroyed (by address string) to // the list of destroyers. In general there will only be one destroyer // but to make it more robust we support multiple. @@ -41,6 +57,9 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error { } key := addr.String() + log.Printf( + "[TRACE] DestroyEdgeTransformer: %s destroying %q", + dag.VertexName(dn), key) destroyers[key] = append(destroyers[key], dn) } @@ -50,13 +69,83 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error { 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. // Use this to find the dependencies, look up if any of them are being // destroyed, and to make the proper edge. - for _, ds := range destroyers { - for _, d := range ds { - // TODO - println(d) + for d, dns := range destroyers { + // d is what is being destroyed. We parse the resource address + // which it came from it is a panic if this fails. + 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)) + } } } diff --git a/terraform/transform_destroy_edge_test.go b/terraform/transform_destroy_edge_test.go new file mode 100644 index 000000000..49b2552be --- /dev/null +++ b/terraform/transform_destroy_edge_test.go @@ -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) +`