diff --git a/terraform/node_resource_abstract.go b/terraform/node_resource_abstract.go index 7d7a21b5e..bdde958bf 100644 --- a/terraform/node_resource_abstract.go +++ b/terraform/node_resource_abstract.go @@ -9,6 +9,13 @@ import ( // abstract resource to a concrete one of some type. type ConcreteResourceNodeFunc func(*NodeAbstractResource) dag.Vertex +// GraphNodeResource is implemented by any nodes that represent a resource. +// The type of operation cannot be assumed, only that this node represents +// the given resource. +type GraphNodeResource interface { + ResourceAddr() *ResourceAddress +} + // NodeAbstractResource represents a resource that has no associated // operations. It registers all the interfaces for a resource that common // across multiple operation types. @@ -99,7 +106,7 @@ func (n *NodeAbstractResource) ProvisionedBy() []string { return result } -// GraphNodeAttachResourceState +// GraphNodeResource, GraphNodeAttachResourceState func (n *NodeAbstractResource) ResourceAddr() *ResourceAddress { return n.Addr } diff --git a/terraform/transform_config_flat.go b/terraform/transform_config_flat.go index 9d13130d6..92f9888d6 100644 --- a/terraform/transform_config_flat.go +++ b/terraform/transform_config_flat.go @@ -11,7 +11,10 @@ import ( // to the graph. The module used to configure this transformer must be // the root module. // -// In relation to ConfigTransformer: this is a newer generation config +// This transform adds the nodes but doesn't connect any of the references. +// The ReferenceTransformer should be used for that. +// +// NOTE: In relation to ConfigTransformer: this is a newer generation config // transformer. It puts the _entire_ config into the graph (there is no // "flattening" step as before). type FlatConfigTransformer struct { @@ -59,7 +62,12 @@ func (t *FlatConfigTransformer) transform(g *Graph, m *module.Tree) error { } addr.Path = m.Path() - abstract := &NodeAbstractResource{Addr: addr} + // Build the abstract resource. We have the config already so + // we'll just pre-populate that. + abstract := &NodeAbstractResource{ + Addr: addr, + Config: r, + } var node dag.Vertex = abstract if f := t.Concrete; f != nil { node = f(abstract) diff --git a/terraform/transform_destroy_cbd.go b/terraform/transform_destroy_cbd.go index b409b2304..0dde08888 100644 --- a/terraform/transform_destroy_cbd.go +++ b/terraform/transform_destroy_cbd.go @@ -4,6 +4,7 @@ import ( "log" "github.com/hashicorp/terraform/config/module" + "github.com/hashicorp/terraform/dag" ) // GraphNodeDestroyerCBD must be implemented by nodes that might be @@ -39,6 +40,7 @@ func (t *CBDEdgeTransformer) Transform(g *Graph) error { log.Printf("[TRACE] CBDEdgeTransformer: Beginning CBD transformation...") // Go through and reverse any destroy edges + destroyMap := make(map[string][]dag.Vertex) for _, v := range g.Vertices() { dn, ok := v.(GraphNodeDestroyerCBD) if !ok { @@ -57,11 +59,132 @@ func (t *CBDEdgeTransformer) Transform(g *Graph) error { continue } + log.Printf("[TRACE] CBDEdgeTransformer: inverting edge: %s => %s", + dag.VertexName(de.Source()), dag.VertexName(de.Target())) + // Found it! Invert. g.RemoveEdge(de) g.Connect(&DestroyEdge{S: de.Target(), T: de.Source()}) } + + // Add this to the list of nodes that we need to fix up + // the edges for (step 2 above in the docs). + key := dn.DestroyAddr().String() + destroyMap[key] = append(destroyMap[key], v) + } + + // If we have no CBD nodes, then our work here is done + if len(destroyMap) == 0 { + return nil + } + + // We have CBD nodes. We now have to move on to the much more difficult + // task of connecting dependencies of the creation side of the destroy + // to the destruction node. The easiest way to explain this is an example: + // + // Given a pre-destroy dependence of: A => B + // And A has CBD set. + // + // The resulting graph should be: A => B => A_d + // + // They key here is that B happens before A is destroyed. This is to + // facilitate the primary purpose for CBD: making sure that downstreams + // are properly updated to avoid downtime before the resource is destroyed. + // + // We can't trust that the resource being destroyed or anything that + // depends on it is actually in our current graph so we make a new + // graph in order to determine those dependencies and add them in. + log.Printf("[TRACE] CBDEdgeTransformer: building graph to find dependencies...") + depMap, err := t.depMap(destroyMap) + if err != nil { + return err + } + + // We now have the mapping of resource addresses to the destroy + // nodes they need to depend on. We now go through our own vertices to + // find any matching these addresses and make the connection. + for _, v := range g.Vertices() { + // We're looking for creators + rn, ok := v.(GraphNodeCreator) + if !ok { + continue + } + + // Get the address + addr := rn.CreateAddr() + key := addr.String() + + // If there is nothing this resource should depend on, ignore it + dns, ok := depMap[key] + if !ok { + continue + } + + // We have nodes! Make the connection + for _, dn := range dns { + log.Printf("[TRACE] CBDEdgeTransformer: destroy depends on dependence: %s => %s", + dag.VertexName(dn), dag.VertexName(v)) + g.Connect(dag.BasicEdge(dn, v)) + } } return nil } + +func (t *CBDEdgeTransformer) depMap( + destroyMap map[string][]dag.Vertex) (map[string][]dag.Vertex, error) { + // Build the graph of our config, this ensures that all resources + // are present in the graph. + g, err := (&BasicGraphBuilder{ + Steps: []GraphTransformer{ + &FlatConfigTransformer{Module: t.Module}, + &AttachResourceConfigTransformer{Module: t.Module}, + &AttachStateTransformer{State: t.State}, + &ReferenceTransformer{}, + }, + }).Build(nil) + if err != nil { + return nil, err + } + + // Using this graph, build the list of destroy nodes that each resource + // address should depend on. For example, when we find B, we map the + // address of B to A_d in the "depMap" variable below. + depMap := make(map[string][]dag.Vertex) + for _, v := range g.Vertices() { + // We're looking for resources. + rn, ok := v.(GraphNodeResource) + if !ok { + continue + } + + // Get the address + addr := rn.ResourceAddr() + key := addr.String() + + // Get the destroy nodes that are destroying this resource. + // If there aren't any, then we don't need to worry about + // any connections. + dns, ok := destroyMap[key] + if !ok { + continue + } + + // Get the nodes that depend on this on. In the example above: + // finding B in A => B. + for _, v := range g.UpEdges(v).List() { + // We're looking for resources. + rn, ok := v.(GraphNodeResource) + if !ok { + continue + } + + // Keep track of the destroy nodes that this address + // needs to depend on. + key := rn.ResourceAddr().String() + depMap[key] = append(depMap[key], dns...) + } + } + + return depMap, nil +} diff --git a/terraform/transform_destroy_cbd_test.go b/terraform/transform_destroy_cbd_test.go index 15a3ea436..dad1d27bb 100644 --- a/terraform/transform_destroy_cbd_test.go +++ b/terraform/transform_destroy_cbd_test.go @@ -11,9 +11,11 @@ func TestCBDEdgeTransformer(t *testing.T) { g.Add(&graphNodeCreatorTest{AddrString: "test.B"}) g.Add(&graphNodeDestroyerTest{AddrString: "test.A", CBD: true}) + module := testModule(t, "transform-destroy-edge-basic") + { tf := &DestroyEdgeTransformer{ - Module: testModule(t, "transform-destroy-edge-basic"), + Module: module, } if err := tf.Transform(&g); err != nil { t.Fatalf("err: %s", err) @@ -21,7 +23,7 @@ func TestCBDEdgeTransformer(t *testing.T) { } { - tf := &CBDEdgeTransformer{} + tf := &CBDEdgeTransformer{Module: module} if err := tf.Transform(&g); err != nil { t.Fatalf("err: %s", err) }