From b8adf102362ea9a133d373362f6ba58916b1b3c2 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Tue, 15 Nov 2016 15:33:40 -0500 Subject: [PATCH] Add debug command with json2dot Add `terraform debug json2dot` to convert debug log graphs to dot format. This is not meant to be in place of more advanced debug visualization, but may continue to be a useful way to work with the debug output. --- command/debug_command.go | 30 ++++++++++++++++ command/debug_json2dot.go | 63 ++++++++++++++++++++++++++++++++++ command/debug_json2dot_test.go | 53 ++++++++++++++++++++++++++++ commands.go | 13 +++++++ dag/marshal.go | 14 ++++++++ 5 files changed, 173 insertions(+) create mode 100644 command/debug_command.go create mode 100644 command/debug_json2dot.go create mode 100644 command/debug_json2dot_test.go diff --git a/command/debug_command.go b/command/debug_command.go new file mode 100644 index 000000000..7058553b9 --- /dev/null +++ b/command/debug_command.go @@ -0,0 +1,30 @@ +package command + +import ( + "strings" + + "github.com/mitchellh/cli" +) + +// DebugCommand is a Command implementation that just shows help for +// the subcommands nested below it. +type DebugCommand struct { + Meta +} + +func (c *DebugCommand) Run(args []string) int { + return cli.RunResultHelp +} + +func (c *DebugCommand) Help() string { + helpText := ` +Usage: terraform debug [options] [args] + + This command has subcommands for debug output management +` + return strings.TrimSpace(helpText) +} + +func (c *DebugCommand) Synopsis() string { + return "Debug output management (experimental)" +} diff --git a/command/debug_json2dot.go b/command/debug_json2dot.go new file mode 100644 index 000000000..f6ed214b0 --- /dev/null +++ b/command/debug_json2dot.go @@ -0,0 +1,63 @@ +package command + +import ( + "fmt" + "os" + "strings" + + "github.com/hashicorp/terraform/dag" + "github.com/mitchellh/cli" +) + +// DebugJSON2DotCommand is a Command implementation that translates a json +// graph debug log to Dot format. +type DebugJSON2DotCommand struct { + Meta +} + +func (c *DebugJSON2DotCommand) Run(args []string) int { + args = c.Meta.process(args, true) + cmdFlags := c.Meta.flagSet("debug json2dot") + + if err := cmdFlags.Parse(args); err != nil { + return cli.RunResultHelp + } + + fileName := cmdFlags.Arg(0) + if fileName == "" { + return cli.RunResultHelp + } + + f, err := os.Open(fileName) + if err != nil { + c.Ui.Error(fmt.Sprintf(errInvalidLog, err)) + return cli.RunResultHelp + } + + dot, err := dag.JSON2Dot(f) + if err != nil { + c.Ui.Error(fmt.Sprintf(errInvalidLog, err)) + return cli.RunResultHelp + } + + c.Ui.Output(string(dot)) + return 0 +} + +func (c *DebugJSON2DotCommand) Help() string { + helpText := ` +Usage: terraform debug json2dot input.json + + Translate a graph debug file to dot format. + + This command takes a single json graph log file and converts it to a single + dot graph written to stdout. +` + return strings.TrimSpace(helpText) +} + +func (c *DebugJSON2DotCommand) Synopsis() string { + return "Convert json graph log to dot" +} + +const errInvalidLog = `Error parsing log file: %[1]s` diff --git a/command/debug_json2dot_test.go b/command/debug_json2dot_test.go new file mode 100644 index 000000000..689897ea0 --- /dev/null +++ b/command/debug_json2dot_test.go @@ -0,0 +1,53 @@ +package command + +import ( + "io/ioutil" + "os" + "strings" + "testing" + + "github.com/hashicorp/terraform/dag" + "github.com/mitchellh/cli" +) + +func TestDebugJSON2Dot(t *testing.T) { + // create the graph JSON output + logFile, err := ioutil.TempFile("", "tf") + if err != nil { + t.Fatal(err) + } + defer os.Remove(logFile.Name()) + + var g dag.Graph + g.SetDebugWriter(logFile) + + g.Add(1) + g.Add(2) + g.Add(3) + g.Connect(dag.BasicEdge(1, 2)) + g.Connect(dag.BasicEdge(2, 3)) + + ui := new(cli.MockUi) + c := &DebugJSON2DotCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(testProvider()), + Ui: ui, + }, + } + + args := []string{ + logFile.Name(), + } + if code := c.Run(args); code != 0 { + t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) + } + + output := ui.OutputWriter.String() + if !strings.HasPrefix(output, "digraph {") { + t.Fatalf("doesn't look like digraph: %s", output) + } + + if !strings.Contains(output, `subgraph "root" {`) { + t.Fatalf("doesn't contains root subgraph: %s", output) + } +} diff --git a/commands.go b/commands.go index dccacab2a..fd8b0c57a 100644 --- a/commands.go +++ b/commands.go @@ -37,6 +37,7 @@ func init() { PlumbingCommands = map[string]struct{}{ "state": struct{}{}, // includes all subcommands + "debug": struct{}{}, // includes all subcommands } Commands = map[string]cli.CommandFactory{ @@ -166,6 +167,18 @@ func init() { // Plumbing //----------------------------------------------------------- + "debug": func() (cli.Command, error) { + return &command.DebugCommand{ + Meta: meta, + }, nil + }, + + "debug json2dot": func() (cli.Command, error) { + return &command.DebugJSON2DotCommand{ + Meta: meta, + }, nil + }, + "state": func() (cli.Command, error) { return &command.StateCommand{ Meta: meta, diff --git a/dag/marshal.go b/dag/marshal.go index 32e1e945d..2f4dd91c0 100644 --- a/dag/marshal.go +++ b/dag/marshal.go @@ -434,3 +434,17 @@ func newEdgeDebugInfo(e Edge, info string) *edgeDebugInfo { Info: info, } } + +// JSON2Dot reads a Graph debug log from and io.Reader, and converts the final +// graph dot format. +// +// TODO: Allow returning the output at a certain point during decode. +// Encode extra information from the json log into the Dot. +func JSON2Dot(r io.Reader) ([]byte, error) { + g, err := decodeGraph(r) + if err != nil { + return nil, err + } + + return g.Dot(nil), nil +}