func TestPruneNoopTransformer(t *testing.T) { g := Graph{Path: RootModulePath} a := &testGraphNodeNoop{NameValue: "A"} b := &testGraphNodeNoop{NameValue: "B", Value: true} c := &testGraphNodeNoop{NameValue: "C"} g.Add(a) g.Add(b) g.Add(c) g.Connect(dag.BasicEdge(a, b)) g.Connect(dag.BasicEdge(b, c)) { tf := &PruneNoopTransformer{} if err := tf.Transform(&g); err != nil { t.Fatalf("err: %s", err) } } actual := strings.TrimSpace(g.String()) expected := strings.TrimSpace(testTransformPruneNoopStr) if actual != expected { t.Fatalf("bad:\n\n%s", actual) } }
func TestVertexTransformer(t *testing.T) { var g Graph g.Add(1) g.Add(2) g.Add(3) g.Connect(dag.BasicEdge(1, 2)) g.Connect(dag.BasicEdge(2, 3)) { tf := &VertexTransformer{ Transforms: []GraphVertexTransformer{ &testVertexTransform{Source: 2, Target: 42}, }, } if err := tf.Transform(&g); err != nil { t.Fatalf("err: %s", err) } } actual := strings.TrimSpace(g.String()) expected := strings.TrimSpace(testVertexTransformerStr) if actual != expected { t.Fatalf("bad: %s", actual) } }
func (t *ProxyTransformer) Transform(g *Graph) error { for _, v := range g.Vertices() { pn, ok := v.(GraphNodeProxy) if !ok { continue } // If we don't want to be proxies, don't do it if !pn.Proxy() { continue } // Connect all the things that depend on this to things that // we depend on as the proxy. See docs for GraphNodeProxy for // a visual explanation. for _, s := range g.UpEdges(v).List() { for _, t := range g.DownEdges(v).List() { g.Connect(GraphProxyEdge{ Edge: dag.BasicEdge(s, t), }) } } } return nil }
// ConnectFrom creates an edge by finding the source from a DependableName // and connecting it to the specific vertex. func (g *Graph) ConnectFrom(source string, target dag.Vertex) { g.once.Do(g.init) if source := g.dependableMap[source]; source != nil { g.Connect(dag.BasicEdge(source, target)) } }
func TestExpandTransform(t *testing.T) { var g Graph g.Add(1) g.Add(2) g.Connect(dag.BasicEdge(1, 2)) tf := &ExpandTransform{} out, err := tf.Transform(&testExpandable{ Result: &g, }) if err != nil { t.Fatalf("err: %s", err) } sn, ok := out.(GraphNodeSubgraph) if !ok { t.Fatalf("not subgraph: %#v", out) } actual := strings.TrimSpace(sn.Subgraph().String()) expected := strings.TrimSpace(testExpandTransformStr) if actual != expected { t.Fatalf("bad: %s", actual) } }
func (t *PruneNoopTransformer) Transform(g *Graph) error { // Find the leaves. leaves := make([]dag.Vertex, 0, 10) for _, v := range g.Vertices() { if g.DownEdges(v).Len() == 0 { leaves = append(leaves, v) } } // Do a depth first walk from the leaves and remove things. return g.ReverseDepthFirstWalk(leaves, func(v dag.Vertex, depth int) error { // We need a prunable pn, ok := v.(GraphNodeNoopPrunable) if !ok { return nil } // Start building the noop opts 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) } // Determine if its a noop. If it isn't, just return noop := pn.Noop(&NoopOpts{ Graph: g, Vertex: v, Diff: t.Diff, State: t.State, ModDiff: modDiff, ModState: modState, }) if !noop { return nil } // It is a noop! We first preserve edges. up := g.UpEdges(v).List() for _, downV := range g.DownEdges(v).List() { for _, upV := range up { g.Connect(dag.BasicEdge(upV, downV)) } } // Then remove it g.Remove(v) return nil }) }
func TestModuleInputTransformer(t *testing.T) { var g Graph g.Add(1) g.Add(2) g.Add(3) g.Connect(dag.BasicEdge(1, 2)) g.Connect(dag.BasicEdge(1, 3)) { tf := &ModuleInputTransformer{} if err := tf.Transform(&g); err != nil { t.Fatalf("err: %s", err) } } actual := strings.TrimSpace(g.String()) expected := strings.TrimSpace(testModuleInputTransformStr) if actual != expected { t.Fatalf("bad:\n\n%s", actual) } }
func TestProxyTransformer(t *testing.T) { var g Graph proxy := &testNodeProxy{NameValue: "proxy"} g.Add("A") g.Add("C") g.Add(proxy) g.Connect(dag.BasicEdge("A", proxy)) g.Connect(dag.BasicEdge(proxy, "C")) { tf := &ProxyTransformer{} if err := tf.Transform(&g); err != nil { t.Fatalf("err: %s", err) } } actual := strings.TrimSpace(g.String()) expected := strings.TrimSpace(testProxyTransformStr) if actual != expected { t.Fatalf("bad: %s", actual) } }
// ConnectTo connects a vertex to a raw string of targets that are the // result of DependableName, and returns the list of targets that are missing. func (g *Graph) ConnectTo(v dag.Vertex, targets []string) []string { g.once.Do(g.init) var missing []string for _, t := range targets { if dest := g.dependableMap[t]; dest != nil { g.Connect(dag.BasicEdge(v, dest)) } else { missing = append(missing, t) } } return missing }
func (t *CloseProviderTransformer) Transform(g *Graph) error { pm := providerVertexMap(g) cpm := closeProviderVertexMap(g) var err error for _, v := range g.Vertices() { if pv, ok := v.(GraphNodeProviderConsumer); ok { for _, p := range pv.ProvidedBy() { source := cpm[p] if source == nil { // Create a new graphNodeCloseProvider and add it to the graph source = &graphNodeCloseProvider{ProviderNameValue: p} g.Add(source) // Close node needs to depend on provider provider, ok := pm[p] if !ok { err = multierror.Append(err, fmt.Errorf( "%s: provider %s couldn't be found for closing", dag.VertexName(v), p)) continue } g.Connect(dag.BasicEdge(source, provider)) // Make sure we also add the new graphNodeCloseProvider to the map // so we don't create and add any duplicate graphNodeCloseProviders. cpm[p] = source } // Close node depends on all nodes provided by the provider g.Connect(dag.BasicEdge(source, v)) } } } return err }
func (t *ModuleInputTransformer) Transform(g *Graph) error { // Create the node n := &graphNodeModuleInput{Variables: t.Variables} // Add it to the graph g.Add(n) // Connect the inputs to the bottom of the graph so that it happens // first. for _, v := range g.Vertices() { if v == n { continue } if g.DownEdges(v).Len() == 0 { g.Connect(dag.BasicEdge(v, n)) } } return nil }
func (t *ProviderTransformer) Transform(g *Graph) error { // Go through the other nodes and match them to providers they need var err error m := providerVertexMap(g) for _, v := range g.Vertices() { if pv, ok := v.(GraphNodeProviderConsumer); ok { for _, p := range pv.ProvidedBy() { target := m[p] if target == nil { err = multierror.Append(err, fmt.Errorf( "%s: provider %s couldn't be found", dag.VertexName(v), p)) continue } g.Connect(dag.BasicEdge(v, target)) } } } return err }
func (t *RootTransformer) Transform(g *Graph) error { // If we already have a good root, we're done if _, err := g.Root(); err == nil { return nil } // Add a root var root graphNodeRoot g.Add(root) // Connect the root to all the edges that need it for _, v := range g.Vertices() { if v == root { continue } if g.UpEdges(v).Len() == 0 { g.Connect(dag.BasicEdge(root, v)) } } return nil }
func (t *CloseProvisionerTransformer) Transform(g *Graph) error { m := closeProvisionerVertexMap(g) for _, v := range g.Vertices() { if pv, ok := v.(GraphNodeProvisionerConsumer); ok { for _, p := range pv.ProvisionedBy() { source := m[p] if source == nil { // Create a new graphNodeCloseProvisioner and add it to the graph source = &graphNodeCloseProvisioner{ProvisionerNameValue: p} g.Add(source) // Make sure we also add the new graphNodeCloseProvisioner to the map // so we don't create and add any duplicate graphNodeCloseProvisioners. m[p] = source } g.Connect(dag.BasicEdge(source, v)) } } } return nil }
func (t *DestroyTransformer) transform( g *Graph, mode GraphNodeDestroyMode) ([]dag.Edge, []dag.Edge, 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(mode) 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)) } } return connect, remove, nil }
func (t *CreateBeforeDestroyTransformer) Transform(g *Graph) error { // We "stage" the edge connections/destroys in these slices so that // while we're doing the edge transformations (transpositions) in // the graph, we're not affecting future edge transpositions. These // slices let us stage ALL the changes that WILL happen so that all // of the transformations happen atomically. var connect, destroy []dag.Edge for _, v := range g.Vertices() { // We only care to use the destroy nodes dn, ok := v.(GraphNodeDestroy) if !ok { continue } // If the node doesn't need to create before destroy, then continue if !dn.CreateBeforeDestroy() { continue } // Get the creation side of this node cn := dn.CreateNode() // Take all the things which depend on the creation node and // make them dependencies on the destruction. Clarifying this // with an example: if you have a web server and a load balancer // and the load balancer depends on the web server, then when we // do a create before destroy, we want to make sure the steps are: // // 1.) Create new web server // 2.) Update load balancer // 3.) Delete old web server // // This ensures that. for _, sourceRaw := range g.UpEdges(cn).List() { source := sourceRaw.(dag.Vertex) // If the graph has a "root" node (one added by a RootTransformer and not // just a resource that happens to have no ancestors), we don't want to // add any edges to it, because then it ceases to be a root. if _, ok := source.(graphNodeRoot); ok { continue } connect = append(connect, dag.BasicEdge(dn, source)) } // Swap the edge so that the destroy depends on the creation // happening... connect = append(connect, dag.BasicEdge(dn, cn)) destroy = append(destroy, dag.BasicEdge(cn, dn)) } for _, edge := range connect { g.Connect(edge) } for _, edge := range destroy { g.RemoveEdge(edge) } return nil }