func (t *VertexTransformer) Transform(g *Graph) error { for _, v := range g.Vertices() { for _, vt := range t.Transforms { newV, err := vt.Transform(v) if err != nil { return err } // If the vertex didn't change, then don't do anything more if newV == v { continue } // Vertex changed, replace it within the graph if ok := g.Replace(v, newV); !ok { // This should never happen, big problem return fmt.Errorf( "Failed to replace %s with %s!\n\nSource: %#v\n\nTarget: %#v", dag.VertexName(v), dag.VertexName(newV), v, newV) } // Replace v so that future transforms use the proper vertex v = newV } } return nil }
func (w *ContextGraphWalker) ExitEvalTree( v dag.Vertex, output interface{}, err error) error { log.Printf("[TRACE] Exiting eval tree: %s", dag.VertexName(v)) // Release the semaphore w.Context.parallelSem.Release() if err == nil { return nil } // Acquire the lock because anything is going to require a lock. w.errorLock.Lock() defer w.errorLock.Unlock() // Try to get a validation error out of it. If its not a validation // error, then just record the normal error. verr, ok := err.(*EvalValidateError) if !ok { return err } for _, msg := range verr.Warnings { w.ValidationWarnings = append( w.ValidationWarnings, fmt.Sprintf("%s: %s", dag.VertexName(v), msg)) } for _, e := range verr.Errors { w.ValidationErrors = append( w.ValidationErrors, errwrap.Wrapf(fmt.Sprintf("%s: {{err}}", dag.VertexName(v)), e)) } return nil }
// testGraphHappensBefore is an assertion helper that tests that node // A (dag.VertexName value) happens before node B. func testGraphHappensBefore(t *testing.T, g *Graph, A, B string) { // Find the B vertex var vertexB dag.Vertex for _, v := range g.Vertices() { if dag.VertexName(v) == B { vertexB = v break } } if vertexB == nil { t.Fatalf( "Expected %q before %q. Couldn't find %q in:\n\n%s", A, B, B, g.String()) } // Look at ancestors deps, err := g.Ancestors(vertexB) if err != nil { t.Fatalf("Error: %s in graph:\n\n%s", err, g.String()) } // Make sure B is in there for _, v := range deps.List() { if dag.VertexName(v) == A { // Success return } } t.Fatalf( "Expected %q before %q in:\n\n%s", A, B, g.String()) }
// GraphNodeNoopPrunable func (n *GraphNodeConfigVariable) Noop(opts *NoopOpts) bool { log.Printf("[DEBUG] Checking variable noop: %s", n.Name()) // If we have no diff, always keep this in the graph. We have to do // this primarily for validation: we want to validate that variable // interpolations are valid even if there are no resources that // depend on them. if opts.Diff == nil || opts.Diff.Empty() { log.Printf("[DEBUG] No diff, not a noop") return false } // We have to find our our module diff since we do funky things with // the flat node's implementation of Path() below. modDiff := opts.Diff.ModuleByPath(n.ModulePath) // If we're destroying, we have no need of variables. if modDiff != nil && modDiff.Destroy { log.Printf("[DEBUG] Destroy diff, treating variable as a noop") return true } for _, v := range opts.Graph.UpEdges(opts.Vertex).List() { // This is terrible, but I can't think of a better way to do this. if dag.VertexName(v) == rootNodeName { continue } log.Printf("[DEBUG] Found up edge to %s, var is not noop", dag.VertexName(v)) return false } log.Printf("[DEBUG] No up edges, treating variable as a noop") return true }
func (c *Core) walk(f func(app.App, *app.Context, bool) error) error { root, err := c.appfileCompiled.Graph.Root() if err != nil { return fmt.Errorf( "Error loading app: %s", err) } // Walk the appfile graph. var stop int32 = 0 return c.appfileCompiled.Graph.Walk(func(raw dag.Vertex) (err error) { // If we're told to stop (something else had an error), then stop early. // Graphs walks by default will complete all disjoint parts of the // graph before failing, but Otto doesn't have to do that. if atomic.LoadInt32(&stop) != 0 { return nil } // If we exit with an error, then mark the stop atomic defer func() { if err != nil { atomic.StoreInt32(&stop, 1) } }() // Convert to the rich vertex type so that we can access data v := raw.(*appfile.CompiledGraphVertex) // Do some logging to help ourselves out log.Printf("[DEBUG] core walking app: %s", v.File.Application.Name) // Get the context and app for this appfile appCtx, err := c.appContext(v.File) if err != nil { return fmt.Errorf( "Error loading Appfile for '%s': %s", dag.VertexName(raw), err) } app, err := c.app(appCtx) if err != nil { return fmt.Errorf( "Error loading App implementation for '%s': %s", dag.VertexName(raw), err) } defer maybeClose(app) // Call our callback return f(app, appCtx, raw == root) }) }
func (t *TargetsTransformer) Transform(g *Graph) error { if len(t.Targets) > 0 && len(t.ParsedTargets) == 0 { addrs, err := t.parseTargetAddresses() if err != nil { return err } t.ParsedTargets = addrs } if len(t.ParsedTargets) > 0 { targetedNodes, err := t.selectTargetedNodes(g, t.ParsedTargets) if err != nil { return err } for _, v := range g.Vertices() { removable := false if _, ok := v.(GraphNodeAddressable); ok { removable = true } if vr, ok := v.(RemovableIfNotTargeted); ok { removable = vr.RemoveIfNotTargeted() } if removable && !targetedNodes.Include(v) { log.Printf("[DEBUG] Removing %q, filtered by targeting.", dag.VertexName(v)) g.Remove(v) } } } return nil }
// GraphDot returns the dot formatting of a visual representation of // the given Terraform graph. func GraphDot(g *Graph, opts *GraphDotOpts) string { buf := new(bytes.Buffer) // Start the graph buf.WriteString("digraph {\n") buf.WriteString("\tcompound = true;\n") // Go through all the vertices and draw it vertices := g.Vertices() dotVertices := make(map[dag.Vertex]struct{}, len(vertices)) for _, v := range vertices { if dn, ok := v.(GraphNodeDotter); !ok { continue } else if dn.Dot("fake") == "" { continue } dotVertices[v] = struct{}{} } for v, _ := range dotVertices { dn := v.(GraphNodeDotter) scanner := bufio.NewScanner(strings.NewReader( dn.Dot(dag.VertexName(v)))) for scanner.Scan() { buf.WriteString("\t" + scanner.Text() + "\n") } // Draw all the edges for _, t := range g.DownEdges(v).List() { target := t.(dag.Vertex) if _, ok := dotVertices[target]; !ok { continue } buf.WriteString(fmt.Sprintf( "\t\"%s\" -> \"%s\";\n", dag.VertexName(v), dag.VertexName(target))) } } // End the graph buf.WriteString("}\n") return buf.String() }
// GraphNodeDotter impl. func (n *graphNodeModuleExpanded) DotNode(name string, opts *dag.DotOpts) *dag.DotNode { return &dag.DotNode{ Name: name, Attrs: map[string]string{ "label": dag.VertexName(n.Original), "shape": "component", }, } }
// GraphNodeDotter impl. func (n *graphNodeModuleExpanded) Dot(name string) string { return fmt.Sprintf( "\"%s\" [\n"+ "\tlabel=\"%s\"\n"+ "\tshape=component\n"+ "];", name, dag.VertexName(n.Original)) }
func (c *Core) walk(f func(app.App, *app.Context, bool) error) error { root, err := c.appfileCompiled.Graph.Root() if err != nil { return fmt.Errorf("装载App报错: %s", err) } //Walk the appfile graph var stop int32 = 0 return c.appfileCompiled.Graph.Walk(func(raw dag.Vertex) (err error) { // 如果stop(发生一些错误),那么尽早stop. // If we're told to stop (something else had an error), then stop early. // Graphs walks by default will complete all disjoint parts of the // graph before failing, but Otto doesn't have to do that. if atomic.LoadInt32(&stop) != 0 { return nil } //如果报错退出,我们标记stop atomic defer func() { if err != nil { atomic.StoreInt32(&stop, 1) } }() // 转换至丰富的Vertex以便我们能够访问数据 v := raw.(*appfile.CompiledGraphVertex) // 给appfile获取App上下文 appCtx, err := c.appContext(v.File) if err != nil { return fmt.Errorf( "loading Appfile for '%s': %s 报错", dag.VertexName(raw), err) } app, err := c.app(appCtx) if err != nil { return fmt.Errorf( "获取App实现报错 '%s': %s", dag.VertexName(raw), err) } // 执行回调 return f(app, appCtx, raw == root) }) }
func (t *AttachStateTransformer) Transform(g *Graph) error { // If no state, then nothing to do if t.State == nil { log.Printf("[DEBUG] Not attaching any state: state is nil") return nil } filter := &StateFilter{State: t.State} for _, v := range g.Vertices() { // Only care about nodes requesting we're adding state an, ok := v.(GraphNodeAttachResourceState) if !ok { continue } addr := an.ResourceAddr() // Get the module state results, err := filter.Filter(addr.String()) if err != nil { return err } // Attach the first resource state we get found := false for _, result := range results { if rs, ok := result.Value.(*ResourceState); ok { log.Printf( "[DEBUG] Attaching resource state to %q: %s", dag.VertexName(v), rs) an.AttachResourceState(rs) found = true break } } if !found { log.Printf( "[DEBUG] Resource state not found for %q: %s", dag.VertexName(v), addr) } } return nil }
func (w *ContextGraphWalker) EnterEvalTree(v dag.Vertex, n EvalNode) EvalNode { log.Printf("[TRACE] Entering eval tree: %s", dag.VertexName(v)) // Acquire a lock on the semaphore w.Context.parallelSem.Acquire() // We want to filter the evaluation tree to only include operations // that belong in this operation. return EvalFilter(n, EvalNodeFilterOp(w.Operation)) }
func (t *ExpandTransform) Transform(v dag.Vertex) (dag.Vertex, error) { ev, ok := v.(GraphNodeExpandable) if !ok { // This isn't an expandable vertex, so just ignore it. return v, nil } // Expand the subgraph! log.Printf("[DEBUG] vertex %q: static expanding", dag.VertexName(ev)) return ev.Expand(t.Builder) }
// GraphNodeDestroyEdgeInclude impl. func (n *GraphNodeConfigVariable) DestroyEdgeInclude(v dag.Vertex) bool { // Only include this variable in a destroy edge if the source vertex // "v" has a count dependency on this variable. log.Printf("[DEBUG] DestroyEdgeInclude: Checking: %s", dag.VertexName(v)) cv, ok := v.(GraphNodeCountDependent) if !ok { log.Printf("[DEBUG] DestroyEdgeInclude: Not GraphNodeCountDependent: %s", dag.VertexName(v)) return false } for _, d := range cv.CountDependentOn() { for _, d2 := range n.DependableName() { log.Printf("[DEBUG] DestroyEdgeInclude: d = %s : d2 = %s", d, d2) if d == d2 { return true } } } return false }
func (t *ReferenceTransformer) Transform(g *Graph) error { // Build a reference map so we can efficiently look up the references vs := g.Vertices() m := NewReferenceMap(vs) // Find the things that reference things and connect them for _, v := range vs { parents, _ := m.References(v) parentsDbg := make([]string, len(parents)) for i, v := range parents { parentsDbg[i] = dag.VertexName(v) } log.Printf( "[DEBUG] ReferenceTransformer: %q references: %v", dag.VertexName(v), parentsDbg) for _, parent := range parents { g.Connect(dag.BasicEdge(v, parent)) } } return nil }
// hasDestroyEdgeInPath recursively walks for a destroy edge, ensuring that // a variable both has no immediate destroy edges or any in its full module // path, ensuring that links do not get severed in the middle. func (n *GraphNodeConfigVariable) hasDestroyEdgeInPath(opts *NoopOpts, vertex dag.Vertex) bool { if vertex == nil { vertex = opts.Vertex } log.Printf("[DEBUG] hasDestroyEdgeInPath: Looking for destroy edge: %s - %T", dag.VertexName(vertex), vertex) for _, v := range opts.Graph.UpEdges(vertex).List() { if len(opts.Graph.UpEdges(v).List()) > 1 { if n.hasDestroyEdgeInPath(opts, v) == true { return true } } // Here we borrow the implementation of DestroyEdgeInclude, whose logic // and semantics are exactly what we want here. We add a check for the // the root node, since we have to always depend on its existance. if cv, ok := vertex.(*GraphNodeConfigVariableFlat); ok { if dag.VertexName(v) == rootNodeName || cv.DestroyEdgeInclude(v) { return true } } } return false }
func TestReferenceMapReferencedBy(t *testing.T) { cases := map[string]struct { Nodes []dag.Vertex Check dag.Vertex Result []string }{ "simple": { Nodes: []dag.Vertex{ &graphNodeRefChildTest{ NameValue: "A", Refs: []string{"A"}, }, &graphNodeRefChildTest{ NameValue: "B", Refs: []string{"A"}, }, &graphNodeRefChildTest{ NameValue: "C", Refs: []string{"B"}, }, }, Check: &graphNodeRefParentTest{ NameValue: "foo", Names: []string{"A"}, }, Result: []string{"A", "B"}, }, } for tn, tc := range cases { t.Run(tn, func(t *testing.T) { rm := NewReferenceMap(tc.Nodes) result := rm.ReferencedBy(tc.Check) var resultStr []string for _, v := range result { resultStr = append(resultStr, dag.VertexName(v)) } sort.Strings(resultStr) sort.Strings(tc.Result) if !reflect.DeepEqual(resultStr, tc.Result) { t.Fatalf("bad: %#v", resultStr) } }) } }
func (t *DisableProviderTransformerOld) Transform(g *Graph) error { // Since we're comparing against edges, we need to make sure we connect g.ConnectDependents() for _, v := range g.Vertices() { // We only care about providers pn, ok := v.(GraphNodeProvider) if !ok || pn.ProviderName() == "" { continue } // Go through all the up-edges (things that depend on this // provider) and if any is not a module, then ignore this node. nonModule := false for _, sourceRaw := range g.UpEdges(v).List() { source := sourceRaw.(dag.Vertex) cn, ok := source.(graphNodeConfig) if !ok { nonModule = true break } if cn.ConfigType() != GraphNodeConfigTypeModule { nonModule = true break } } if nonModule { // We found something that depends on this provider that // isn't a module, so skip it. continue } // Disable the provider by replacing it with a "disabled" provider disabled := &graphNodeDisabledProvider{GraphNodeProvider: pn} if !g.Replace(v, disabled) { panic(fmt.Sprintf( "vertex disappeared from under us: %s", dag.VertexName(v))) } } return nil }
// GraphNodeDestroyable impl. func (n *GraphNodeConfigResourceFlat) DestroyNode(mode GraphNodeDestroyMode) GraphNodeDestroy { // Get our parent destroy node. If we don't have any, just return raw := n.GraphNodeConfigResource.DestroyNode(mode) if raw == nil { return nil } node, ok := raw.(*graphNodeResourceDestroy) if !ok { panic(fmt.Sprintf("unknown destroy node: %s %T", dag.VertexName(raw), raw)) } // Otherwise, wrap it so that it gets the proper module treatment. return &graphNodeResourceDestroyFlat{ graphNodeResourceDestroy: node, PathValue: n.PathValue, FlatCreateNode: n, } }
// GraphNodeNoopPrunable func (n *GraphNodeConfigVariable) Noop(opts *NoopOpts) bool { // If we have no diff, always keep this in the graph. We have to do // this primarily for validation: we want to validate that variable // interpolations are valid even if there are no resources that // depend on them. if opts.Diff == nil || opts.Diff.Empty() { return false } for _, v := range opts.Graph.UpEdges(opts.Vertex).List() { // This is terrible, but I can't think of a better way to do this. if dag.VertexName(v) == rootNodeName { continue } return false } return true }
func (t *ProvisionerTransformer) Transform(g *Graph) error { // Go through the other nodes and match them to provisioners they need var err error m := provisionerVertexMap(g) for _, v := range g.Vertices() { if pv, ok := v.(GraphNodeProvisionerConsumer); ok { for _, p := range pv.ProvisionedBy() { if m[p] == nil { err = multierror.Append(err, fmt.Errorf( "%s: provisioner %s couldn't be found", dag.VertexName(v), p)) continue } g.Connect(dag.BasicEdge(v, m[p])) } } } return err }
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() { key := p source := cpm[key] 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[key] 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[key] = source } // Close node depends on all nodes provided by the provider g.Connect(dag.BasicEdge(source, v)) } } } return err }
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[providerMapKey(p, pv)] if target == nil { println(fmt.Sprintf("%#v\n\n%#v", m, providerMapKey(p, pv))) 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 *DisableProviderTransformer) Transform(g *Graph) error { for _, v := range g.Vertices() { // We only care about providers pn, ok := v.(GraphNodeProvider) if !ok || pn.ProviderName() == "" { continue } // If we have dependencies, then don't disable if g.UpEdges(v).Len() > 0 { continue } // Get the path var path []string if pn, ok := v.(GraphNodeSubPath); ok { path = pn.Path() } // Disable the provider by replacing it with a "disabled" provider disabled := &NodeDisabledProvider{ NodeAbstractProvider: &NodeAbstractProvider{ NameValue: pn.ProviderName(), PathValue: path, }, } if !g.Replace(v, disabled) { panic(fmt.Sprintf( "vertex disappeared from under us: %s", dag.VertexName(v))) } } return nil }
func (c *Compiled) Validate() error { var result error // First validate that there are no cycles in the dependency graph if cycles := c.Graph.Cycles(); len(cycles) > 0 { for _, cycle := range cycles { vertices := make([]string, len(cycle)) for i, v := range cycle { vertices[i] = dag.VertexName(v) } result = multierror.Append(result, fmt.Errorf( "Dependency cycle: %s", strings.Join(vertices, ", "))) } } // Validate all the files var errLock sync.Mutex c.Graph.Walk(func(raw dag.Vertex) error { v := raw.(*CompiledGraphVertex) if err := v.File.Validate(); err != nil { errLock.Lock() defer errLock.Unlock() if s := v.File.Source; s != "" { err = multierror.Prefix(err, fmt.Sprintf("Dependency %s:", s)) } result = multierror.Append(result, err) } return nil }) return result }
func (t *TargetsTransformer) Transform(g *Graph) error { if len(t.Targets) > 0 { // TODO: duplicated in OrphanTransformer; pull up parsing earlier addrs, err := t.parseTargetAddresses() if err != nil { return err } targetedNodes, err := t.selectTargetedNodes(g, addrs) if err != nil { return err } for _, v := range g.Vertices() { if _, ok := v.(GraphNodeAddressable); ok { if !targetedNodes.Include(v) { log.Printf("[DEBUG] Removing %q, filtered by targeting.", dag.VertexName(v)) g.Remove(v) } } } } return nil }
// GraphNodeDotter impl. func (n *graphNodeModuleExpanded) DotNode(name string, opts *GraphDotOpts) *dot.Node { return dot.NewNode(name, map[string]string{ "label": dag.VertexName(n.Original), "shape": "component", }) }
func (n *graphNodeModuleExpanded) Name() string { return fmt.Sprintf("%s (expanded)", dag.VertexName(n.Original)) }
func (n *graphNodeDisabledProvider) Name() string { return fmt.Sprintf("%s (disabled)", dag.VertexName(n.GraphNodeProvider)) }
func graphDotNodeName(modName, v dag.Vertex) string { return fmt.Sprintf("[%s] %s", modName, dag.VertexName(v)) }