Replace DebugGraphs with the Graph's methods
Now that the Graph can serialize itself, and log transformations, there's no need for DebugGraph
This commit is contained in:
parent
82b1a2abc2
commit
6f9744292a
|
@ -697,11 +697,9 @@ func (c *Context) walk(
|
||||||
|
|
||||||
log.Printf("[DEBUG] Starting graph walk: %s", operation.String())
|
log.Printf("[DEBUG] Starting graph walk: %s", operation.String())
|
||||||
|
|
||||||
dg, _ := NewDebugGraph("walk", graph, nil)
|
|
||||||
walker := &ContextGraphWalker{
|
walker := &ContextGraphWalker{
|
||||||
Context: realCtx,
|
Context: realCtx,
|
||||||
Operation: operation,
|
Operation: operation,
|
||||||
DebugGraph: dg,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Watch for a stop so we can call the provider Stop() API.
|
// Watch for a stop so we can call the provider Stop() API.
|
||||||
|
@ -728,20 +726,13 @@ func (c *Context) walk(
|
||||||
|
|
||||||
// If we have a shadow graph, wait for that to complete.
|
// If we have a shadow graph, wait for that to complete.
|
||||||
if shadowCloser != nil {
|
if shadowCloser != nil {
|
||||||
// create a debug graph for this walk
|
|
||||||
dg, err := NewDebugGraph("walk-shadow", shadow, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[ERROR] %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the graph walker for the shadow. We also wrap this in
|
// Build the graph walker for the shadow. We also wrap this in
|
||||||
// a panicwrap so that panics are captured. For the shadow graph,
|
// a panicwrap so that panics are captured. For the shadow graph,
|
||||||
// we just want panics to be normal errors rather than to crash
|
// we just want panics to be normal errors rather than to crash
|
||||||
// Terraform.
|
// Terraform.
|
||||||
shadowWalker := GraphWalkerPanicwrap(&ContextGraphWalker{
|
shadowWalker := GraphWalkerPanicwrap(&ContextGraphWalker{
|
||||||
Context: shadowCtx,
|
Context: shadowCtx,
|
||||||
Operation: operation,
|
Operation: operation,
|
||||||
DebugGraph: dg,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Kick off the shadow walk. This will block on any operations
|
// Kick off the shadow walk. This will block on any operations
|
||||||
|
|
|
@ -166,6 +166,41 @@ func (d *debugInfo) Close() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// debug buffer is an io.WriteCloser that will write itself to the debug
|
||||||
|
// archive when closed.
|
||||||
|
type debugBuffer struct {
|
||||||
|
debugInfo *debugInfo
|
||||||
|
name string
|
||||||
|
buf bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *debugBuffer) Write(d []byte) (int, error) {
|
||||||
|
return b.buf.Write(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *debugBuffer) Close() error {
|
||||||
|
return b.debugInfo.WriteFile(b.name, b.buf.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ioutils only has a noop ReadCloser
|
||||||
|
type nopWriteCloser struct{}
|
||||||
|
|
||||||
|
func (nopWriteCloser) Write([]byte) (int, error) { return 0, nil }
|
||||||
|
func (nopWriteCloser) Close() error { return nil }
|
||||||
|
|
||||||
|
// NewFileWriter returns an io.WriteClose that will be buffered and written to
|
||||||
|
// the debug archive when closed.
|
||||||
|
func (d *debugInfo) NewFileWriter(name string) io.WriteCloser {
|
||||||
|
if d == nil {
|
||||||
|
return nopWriteCloser{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &debugBuffer{
|
||||||
|
debugInfo: d,
|
||||||
|
name: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type syncer interface {
|
type syncer interface {
|
||||||
Sync() error
|
Sync() error
|
||||||
}
|
}
|
||||||
|
@ -192,15 +227,10 @@ func (d *debugInfo) flush() {
|
||||||
// WriteGraph takes a DebugGraph and writes both the DebugGraph as a dot file
|
// WriteGraph takes a DebugGraph and writes both the DebugGraph as a dot file
|
||||||
// in the debug archive, and extracts any logs that the DebugGraph collected
|
// in the debug archive, and extracts any logs that the DebugGraph collected
|
||||||
// and writes them to a log file in the archive.
|
// and writes them to a log file in the archive.
|
||||||
func (d *debugInfo) WriteGraph(dg *DebugGraph) error {
|
func (d *debugInfo) WriteGraph(name string, g *Graph) error {
|
||||||
if d == nil {
|
if d == nil || g == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if dg == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
d.Lock()
|
d.Lock()
|
||||||
defer d.Unlock()
|
defer d.Unlock()
|
||||||
|
|
||||||
|
@ -209,12 +239,10 @@ func (d *debugInfo) WriteGraph(dg *DebugGraph) error {
|
||||||
// sync'ed.
|
// sync'ed.
|
||||||
defer d.flush()
|
defer d.flush()
|
||||||
|
|
||||||
d.writeFile(dg.Name, dg.LogBytes())
|
dotPath := fmt.Sprintf("%s/graphs/%d-%s-%s.dot", d.name, d.step, d.phase, name)
|
||||||
|
|
||||||
dotPath := fmt.Sprintf("%s/graphs/%d-%s-%s.dot", d.name, d.step, d.phase, dg.Name)
|
|
||||||
d.step++
|
d.step++
|
||||||
|
|
||||||
dotBytes := dg.DotBytes()
|
dotBytes := g.Dot(nil)
|
||||||
hdr := &tar.Header{
|
hdr := &tar.Header{
|
||||||
Name: dotPath,
|
Name: dotPath,
|
||||||
Mode: 0644,
|
Mode: 0644,
|
||||||
|
|
|
@ -16,7 +16,7 @@ func TestDebugInfo_nil(t *testing.T) {
|
||||||
var d *debugInfo
|
var d *debugInfo
|
||||||
|
|
||||||
d.SetPhase("none")
|
d.SetPhase("none")
|
||||||
d.WriteGraph(nil)
|
d.WriteGraph("", nil)
|
||||||
d.WriteFile("none", nil)
|
d.WriteFile("none", nil)
|
||||||
d.Close()
|
d.Close()
|
||||||
}
|
}
|
||||||
|
@ -122,6 +122,7 @@ func TestDebug_plan(t *testing.T) {
|
||||||
|
|
||||||
files := 0
|
files := 0
|
||||||
graphs := 0
|
graphs := 0
|
||||||
|
json := 0
|
||||||
for {
|
for {
|
||||||
hdr, err := tr.Next()
|
hdr, err := tr.Next()
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
|
@ -139,6 +140,10 @@ func TestDebug_plan(t *testing.T) {
|
||||||
if strings.HasSuffix(hdr.Name, ".dot") {
|
if strings.HasSuffix(hdr.Name, ".dot") {
|
||||||
graphs++
|
graphs++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasSuffix(hdr.Name, "graph.json") {
|
||||||
|
json++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,13 +151,13 @@ func TestDebug_plan(t *testing.T) {
|
||||||
t.Fatal("no files with data found")
|
t.Fatal("no files with data found")
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
if graphs == 0 {
|
||||||
TODO: once @jbardin finishes the dot refactor, uncomment this. This
|
t.Fatal("no no-empty graphs found")
|
||||||
won't pass since the new graph doesn't implement the dot nodes.
|
}
|
||||||
if graphs == 0 {
|
|
||||||
t.Fatal("no no-empty graphs found")
|
if json == 0 {
|
||||||
}
|
t.Fatal("no json graphs")
|
||||||
*/
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify that no hooks panic on nil input
|
// verify that no hooks panic on nil input
|
||||||
|
|
|
@ -40,6 +40,11 @@ type Graph struct {
|
||||||
// edges.
|
// edges.
|
||||||
dependableMap map[string]dag.Vertex
|
dependableMap map[string]dag.Vertex
|
||||||
|
|
||||||
|
// debugName is a name for reference in the debug output. This is usually
|
||||||
|
// to indicate what topmost builder was, and if this graph is a shadow or
|
||||||
|
// not.
|
||||||
|
debugName string
|
||||||
|
|
||||||
once sync.Once
|
once sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,7 +206,6 @@ func (g *Graph) Dependable(n string) dag.Vertex {
|
||||||
// will be walked with full parallelism, so the walker should expect
|
// will be walked with full parallelism, so the walker should expect
|
||||||
// to be called in concurrently.
|
// to be called in concurrently.
|
||||||
func (g *Graph) Walk(walker GraphWalker) error {
|
func (g *Graph) Walk(walker GraphWalker) error {
|
||||||
defer dbug.WriteGraph(walker.Debug())
|
|
||||||
return g.walk(walker)
|
return g.walk(walker)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,6 +233,15 @@ func (g *Graph) walk(walker GraphWalker) error {
|
||||||
panicwrap = nil // just to be sure
|
panicwrap = nil // just to be sure
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debugName := "walk-graph.json"
|
||||||
|
if g.debugName != "" {
|
||||||
|
debugName = g.debugName + "-" + debugName
|
||||||
|
}
|
||||||
|
|
||||||
|
debugBuf := dbug.NewFileWriter(debugName)
|
||||||
|
g.SetDebugWriter(debugBuf)
|
||||||
|
defer debugBuf.Close()
|
||||||
|
|
||||||
// Walk the graph.
|
// Walk the graph.
|
||||||
var walkFn dag.WalkFunc
|
var walkFn dag.WalkFunc
|
||||||
walkFn = func(v dag.Vertex) (rerr error) {
|
walkFn = func(v dag.Vertex) (rerr error) {
|
||||||
|
@ -258,10 +271,7 @@ func (g *Graph) walk(walker GraphWalker) error {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
walker.EnterVertex(v)
|
walker.EnterVertex(v)
|
||||||
defer func() {
|
defer walker.ExitVertex(v, rerr)
|
||||||
walker.Debug().DebugNode(v)
|
|
||||||
walker.ExitVertex(v, rerr)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// vertexCtx is the context that we use when evaluating. This
|
// vertexCtx is the context that we use when evaluating. This
|
||||||
// is normally the context of our graph but can be overridden
|
// is normally the context of our graph but can be overridden
|
||||||
|
@ -283,7 +293,10 @@ func (g *Graph) walk(walker GraphWalker) error {
|
||||||
// Allow the walker to change our tree if needed. Eval,
|
// Allow the walker to change our tree if needed. Eval,
|
||||||
// then callback with the output.
|
// then callback with the output.
|
||||||
log.Printf("[DEBUG] vertex '%s.%s': evaluating", path, dag.VertexName(v))
|
log.Printf("[DEBUG] vertex '%s.%s': evaluating", path, dag.VertexName(v))
|
||||||
walker.Debug().Printf("[DEBUG] vertex %T(%s.%s): evaluating\n", v, path, dag.VertexName(v))
|
|
||||||
|
// TODO: replace these debug calls with Graph native methods
|
||||||
|
//walker.Debug().Printf("[DEBUG] vertex %T(%s.%s): evaluating\n", v, path, dag.VertexName(v))
|
||||||
|
|
||||||
tree = walker.EnterEvalTree(v, tree)
|
tree = walker.EnterEvalTree(v, tree)
|
||||||
output, err := Eval(tree, vertexCtx)
|
output, err := Eval(tree, vertexCtx)
|
||||||
if rerr = walker.ExitEvalTree(v, output, err); rerr != nil {
|
if rerr = walker.ExitEvalTree(v, output, err); rerr != nil {
|
||||||
|
@ -297,7 +310,7 @@ func (g *Graph) walk(walker GraphWalker) error {
|
||||||
"[DEBUG] vertex '%s.%s': expanding/walking dynamic subgraph",
|
"[DEBUG] vertex '%s.%s': expanding/walking dynamic subgraph",
|
||||||
path,
|
path,
|
||||||
dag.VertexName(v))
|
dag.VertexName(v))
|
||||||
walker.Debug().Printf("[DEBUG] vertex %T(%s.%s): expanding\n", v, path, dag.VertexName(v))
|
//walker.Debug().Printf("[DEBUG] vertex %T(%s.%s): expanding\n", v, path, dag.VertexName(v))
|
||||||
g, err := ev.DynamicExpand(vertexCtx)
|
g, err := ev.DynamicExpand(vertexCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rerr = err
|
rerr = err
|
||||||
|
@ -318,8 +331,8 @@ func (g *Graph) walk(walker GraphWalker) error {
|
||||||
path,
|
path,
|
||||||
dag.VertexName(v))
|
dag.VertexName(v))
|
||||||
|
|
||||||
walker.Debug().Printf(
|
//walker.Debug().Printf(
|
||||||
"[DEBUG] vertex %T(%s.%s): subgraph\n", v, path, dag.VertexName(v))
|
// "[DEBUG] vertex %T(%s.%s): subgraph\n", v, path, dag.VertexName(v))
|
||||||
|
|
||||||
if rerr = sn.Subgraph().(*Graph).walk(walker); rerr != nil {
|
if rerr = sn.Subgraph().(*Graph).walk(walker); rerr != nil {
|
||||||
return
|
return
|
||||||
|
|
|
@ -29,6 +29,15 @@ type BasicGraphBuilder struct {
|
||||||
|
|
||||||
func (b *BasicGraphBuilder) Build(path []string) (*Graph, error) {
|
func (b *BasicGraphBuilder) Build(path []string) (*Graph, error) {
|
||||||
g := &Graph{Path: path}
|
g := &Graph{Path: path}
|
||||||
|
|
||||||
|
debugName := "build-graph.json"
|
||||||
|
if b.Name != "" {
|
||||||
|
debugName = b.Name + "-" + debugName
|
||||||
|
}
|
||||||
|
debugBuf := dbug.NewFileWriter(debugName)
|
||||||
|
g.SetDebugWriter(debugBuf)
|
||||||
|
defer debugBuf.Close()
|
||||||
|
|
||||||
for _, step := range b.Steps {
|
for _, step := range b.Steps {
|
||||||
if step == nil {
|
if step == nil {
|
||||||
continue
|
continue
|
||||||
|
@ -52,8 +61,8 @@ func (b *BasicGraphBuilder) Build(path []string) (*Graph, error) {
|
||||||
"[TRACE] Graph after step %T:\n\n%s",
|
"[TRACE] Graph after step %T:\n\n%s",
|
||||||
step, g.StringWithNodeTypes())
|
step, g.StringWithNodeTypes())
|
||||||
|
|
||||||
dg, _ := NewDebugGraph(debugName, g, nil)
|
// TODO: replace entirely with the json logs
|
||||||
dbug.WriteGraph(dg)
|
dbug.WriteGraph(debugName, g)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return g, err
|
return g, err
|
||||||
|
|
|
@ -1,111 +0,0 @@
|
||||||
package terraform
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
"github.com/hashicorp/terraform/dag"
|
|
||||||
"github.com/mitchellh/copystructure"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The NodeDebug method outputs debug information to annotate the graphs
|
|
||||||
// stored in the DebugInfo
|
|
||||||
type GraphNodeDebugger interface {
|
|
||||||
NodeDebug() string
|
|
||||||
}
|
|
||||||
|
|
||||||
type GraphNodeDebugOrigin interface {
|
|
||||||
DotOrigin() bool
|
|
||||||
}
|
|
||||||
type DebugGraph struct {
|
|
||||||
sync.Mutex
|
|
||||||
Name string
|
|
||||||
|
|
||||||
ord int
|
|
||||||
buf bytes.Buffer
|
|
||||||
|
|
||||||
Graph *Graph
|
|
||||||
|
|
||||||
dotOpts *dag.DotOpts
|
|
||||||
}
|
|
||||||
|
|
||||||
// DebugGraph holds a dot representation of the Terraform graph, and can be
|
|
||||||
// written out to the DebugInfo log with DebugInfo.WriteGraph. A DebugGraph can
|
|
||||||
// log data to it's internal buffer via the Printf and Write methods, which
|
|
||||||
// will be also be written out to the DebugInfo archive.
|
|
||||||
func NewDebugGraph(name string, g *Graph, opts *dag.DotOpts) (*DebugGraph, error) {
|
|
||||||
dg := &DebugGraph{
|
|
||||||
Name: name,
|
|
||||||
Graph: g,
|
|
||||||
dotOpts: opts,
|
|
||||||
}
|
|
||||||
|
|
||||||
dbug.WriteFile(dg.Name, g.Dot(opts))
|
|
||||||
return dg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Printf to the internal buffer
|
|
||||||
func (dg *DebugGraph) Printf(f string, args ...interface{}) (int, error) {
|
|
||||||
if dg == nil {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
dg.Lock()
|
|
||||||
defer dg.Unlock()
|
|
||||||
return fmt.Fprintf(&dg.buf, f, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write to the internal buffer
|
|
||||||
func (dg *DebugGraph) Write(b []byte) (int, error) {
|
|
||||||
if dg == nil {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
dg.Lock()
|
|
||||||
defer dg.Unlock()
|
|
||||||
return dg.buf.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dg *DebugGraph) LogBytes() []byte {
|
|
||||||
if dg == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
dg.Lock()
|
|
||||||
defer dg.Unlock()
|
|
||||||
return dg.buf.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dg *DebugGraph) DotBytes() []byte {
|
|
||||||
if dg == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
dg.Lock()
|
|
||||||
defer dg.Unlock()
|
|
||||||
return dg.Graph.Dot(dg.dotOpts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dg *DebugGraph) DebugNode(v interface{}) {
|
|
||||||
if dg == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dg.Lock()
|
|
||||||
defer dg.Unlock()
|
|
||||||
|
|
||||||
// record the ordinal value for each node
|
|
||||||
ord := dg.ord
|
|
||||||
dg.ord++
|
|
||||||
|
|
||||||
name := dag.VertexName(v)
|
|
||||||
vCopy, _ := copystructure.Config{Lock: true}.Copy(v)
|
|
||||||
|
|
||||||
// record as much of the node data structure as we can
|
|
||||||
spew.Fdump(&dg.buf, vCopy)
|
|
||||||
|
|
||||||
dg.buf.WriteString(fmt.Sprintf("%d visited %s\n", ord, name))
|
|
||||||
|
|
||||||
// if the node provides debug output, insert it into the graph, and log it
|
|
||||||
if nd, ok := v.(GraphNodeDebugger); ok {
|
|
||||||
out := nd.NodeDebug()
|
|
||||||
dg.buf.WriteString(fmt.Sprintf("NodeDebug (%s):'%s'\n", name, out))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -13,7 +13,6 @@ type GraphWalker interface {
|
||||||
ExitVertex(dag.Vertex, error)
|
ExitVertex(dag.Vertex, error)
|
||||||
EnterEvalTree(dag.Vertex, EvalNode) EvalNode
|
EnterEvalTree(dag.Vertex, EvalNode) EvalNode
|
||||||
ExitEvalTree(dag.Vertex, interface{}, error) error
|
ExitEvalTree(dag.Vertex, interface{}, error) error
|
||||||
Debug() *DebugGraph
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GrpahWalkerPanicwrapper can be optionally implemented to catch panics
|
// GrpahWalkerPanicwrapper can be optionally implemented to catch panics
|
||||||
|
@ -59,4 +58,3 @@ func (NullGraphWalker) EnterEvalTree(v dag.Vertex, n EvalNode) EvalNode { return
|
||||||
func (NullGraphWalker) ExitEvalTree(dag.Vertex, interface{}, error) error {
|
func (NullGraphWalker) ExitEvalTree(dag.Vertex, interface{}, error) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (NullGraphWalker) Debug() *DebugGraph { return nil }
|
|
||||||
|
|
|
@ -15,9 +15,8 @@ type ContextGraphWalker struct {
|
||||||
NullGraphWalker
|
NullGraphWalker
|
||||||
|
|
||||||
// Configurable values
|
// Configurable values
|
||||||
Context *Context
|
Context *Context
|
||||||
Operation walkOperation
|
Operation walkOperation
|
||||||
DebugGraph *DebugGraph
|
|
||||||
|
|
||||||
// Outputs, do not set these. Do not read these while the graph
|
// Outputs, do not set these. Do not read these while the graph
|
||||||
// is being walked.
|
// is being walked.
|
||||||
|
@ -145,10 +144,6 @@ func (w *ContextGraphWalker) ExitEvalTree(
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *ContextGraphWalker) Debug() *DebugGraph {
|
|
||||||
return w.DebugGraph
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *ContextGraphWalker) init() {
|
func (w *ContextGraphWalker) init() {
|
||||||
w.contexts = make(map[string]*BuiltinEvalContext, 5)
|
w.contexts = make(map[string]*BuiltinEvalContext, 5)
|
||||||
w.providerCache = make(map[string]ResourceProvider, 5)
|
w.providerCache = make(map[string]ResourceProvider, 5)
|
||||||
|
|
Loading…
Reference in New Issue