From 6c694014b4716ad5debbc8a4dca07a2a75cf3d10 Mon Sep 17 00:00:00 2001 From: Kenjiro Nakayama Date: Sat, 30 Jul 2016 19:20:43 +0900 Subject: [PATCH 1/8] Support JSON and template format with nomad CLI --- command/alloc_status.go | 30 +++++++++++++++++-- command/data_format.go | 58 +++++++++++++++++++++++++++++++++++++ command/data_format_test.go | 41 ++++++++++++++++++++++++++ command/eval_status.go | 26 +++++++++++++++++ command/node_status.go | 27 +++++++++++++++++ command/status.go | 26 +++++++++++++++++ 6 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 command/data_format.go create mode 100644 command/data_format_test.go diff --git a/command/alloc_status.go b/command/alloc_status.go index ebf83163eb0..21cc39e6a09 100644 --- a/command/alloc_status.go +++ b/command/alloc_status.go @@ -38,11 +38,17 @@ Alloc Status Options: -short Display short output. Shows only the most recent task event. - -stats - Display detailed resource usage statistics + -stats + Display detailed resource usage statistics. -verbose Show full information. + + -format + Display specified format, "json" or "template". + + -t + Sets the template with golang templates format. ` return strings.TrimSpace(helpText) @@ -54,12 +60,15 @@ func (c *AllocStatusCommand) Synopsis() string { func (c *AllocStatusCommand) Run(args []string) int { var short, displayStats, verbose bool + var format, tmpl string flags := c.Meta.FlagSet("alloc-status", FlagSetClient) flags.Usage = func() { c.Ui.Output(c.Help()) } flags.BoolVar(&short, "short", false, "") flags.BoolVar(&verbose, "verbose", false, "") flags.BoolVar(&displayStats, "stats", false, "") + flags.StringVar(&format, "format", "default", "") + flags.StringVar(&tmpl, "t", "", "") if err := flags.Parse(args); err != nil { return 1 @@ -130,6 +139,23 @@ func (c *AllocStatusCommand) Run(args []string) int { return 1 } + // If output format is specified, format and output the data + if format != "default" { + f, err := DataFormat(format, tmpl) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error getting formatter: %s", err)) + return 1 + } + + out, err := f.TransformData(alloc) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error transform the data: %s", err)) + return 1 + } + c.Ui.Output(out) + return 0 + } + var statsErr error var stats *api.AllocResourceUsage stats, statsErr = client.Allocations().Stats(alloc, nil) diff --git a/command/data_format.go b/command/data_format.go new file mode 100644 index 00000000000..1111ca0ceb5 --- /dev/null +++ b/command/data_format.go @@ -0,0 +1,58 @@ +package command + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "text/template" +) + +//DataFormatter is a transformer of the data. +type DataFormatter interface { + // TransformData should return transformed string data. + TransformData(interface{}) (string, error) +} + +// DataFormat returns the data formatter specified format. +func DataFormat(format, tmpl string) (DataFormatter, error) { + switch format { + case "json": + return &JSONFormat{}, nil + case "template": + return &TemplateFormat{tmpl}, nil + } + return nil, fmt.Errorf("Unsupported format is specified.") +} + +type JSONFormat struct { +} + +// TransformData returns JSON format string data. +func (p *JSONFormat) TransformData(data interface{}) (string, error) { + out, err := json.MarshalIndent(&data, "", " ") + if err != nil { + return "", err + } + + return string(out), nil +} + +type TemplateFormat struct { + tmpl string +} + +// TransformData returns template format string data. +func (p *TemplateFormat) TransformData(data interface{}) (string, error) { + var out io.Writer = new(bytes.Buffer) + if len(p.tmpl) == 0 { + return "", fmt.Errorf("template needs to be specified the golang templates.") + } + + t := template.Must(template.New("format").Parse(p.tmpl)) + err := t.Execute(out, data) + if err != nil { + return "", err + } + return fmt.Sprint(out), nil +} diff --git a/command/data_format_test.go b/command/data_format_test.go new file mode 100644 index 00000000000..97335505a21 --- /dev/null +++ b/command/data_format_test.go @@ -0,0 +1,41 @@ +package command + +import ( + "testing" +) + +type testData struct { + Region string + ID string + Name string +} + +const expectJSON = `{ + "Region": "global", + "ID": "1", + "Name": "example" +}` + +var ( + tData = testData{"global", "1", "example"} + testFormat = map[string]string{"json": "", "template": "{{.Region}}"} + expectOutput = map[string]string{"json": expectJSON, "template": "global"} +) + +func TestDataFormat(t *testing.T) { + for k, v := range testFormat { + fm, err := DataFormat(k, v) + if err != nil { + t.Fatalf("err: %v", err) + } + + result, err := fm.TransformData(tData) + if err != nil { + t.Fatalf("err: %v", err) + } + + if result != expectOutput[k] { + t.Fatalf("expected output: %s, actual: %s", expectOutput[k], result) + } + } +} diff --git a/command/eval_status.go b/command/eval_status.go index 8a87e96bda7..ff8becd663f 100644 --- a/command/eval_status.go +++ b/command/eval_status.go @@ -31,6 +31,12 @@ Eval Status Options: -verbose Show full information. + + -format + Display specified format, "json" or "template". + + -t + Sets the template with golang templates format. ` return strings.TrimSpace(helpText) @@ -42,11 +48,14 @@ func (c *EvalStatusCommand) Synopsis() string { func (c *EvalStatusCommand) Run(args []string) int { var monitor, verbose bool + var format, tmpl string flags := c.Meta.FlagSet("eval-status", FlagSetClient) flags.Usage = func() { c.Ui.Output(c.Help()) } flags.BoolVar(&monitor, "monitor", false, "") flags.BoolVar(&verbose, "verbose", false, "") + flags.StringVar(&format, "format", "default", "") + flags.StringVar(&tmpl, "t", "", "") if err := flags.Parse(args); err != nil { return 1 @@ -124,6 +133,23 @@ func (c *EvalStatusCommand) Run(args []string) int { return 1 } + // If output format is specified, format and output the data + if format != "default" { + f, err := DataFormat(format, tmpl) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error getting formatter: %s", err)) + return 1 + } + + out, err := f.TransformData(eval) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error transform the data: %s", err)) + return 1 + } + c.Ui.Output(out) + return 0 + } + failureString, failures := evalFailureStatus(eval) triggerNoun, triggerSubj := getTriggerDetails(eval) statusDesc := eval.StatusDescription diff --git a/command/node_status.go b/command/node_status.go index 36e1ad4dfcb..274bc8967ce 100644 --- a/command/node_status.go +++ b/command/node_status.go @@ -30,6 +30,8 @@ type NodeStatusCommand struct { list_allocs bool self bool stats bool + format string + tmpl string } func (c *NodeStatusCommand) Help() string { @@ -66,6 +68,12 @@ Node Status Options: -verbose Display full information. + + -format + Display specified format, "json" or "template". + + -t + Sets the template with golang templates format. ` return strings.TrimSpace(helpText) } @@ -83,6 +91,8 @@ func (c *NodeStatusCommand) Run(args []string) int { flags.BoolVar(&c.list_allocs, "allocs", false, "") flags.BoolVar(&c.self, "self", false, "") flags.BoolVar(&c.stats, "stats", false, "") + flags.StringVar(&c.format, "format", "default", "") + flags.StringVar(&c.tmpl, "t", "", "") if err := flags.Parse(args); err != nil { return 1 @@ -216,6 +226,23 @@ func (c *NodeStatusCommand) Run(args []string) int { return 1 } + // If output format is specified, format and output the data + if c.format != "default" { + f, err := DataFormat(c.format, c.tmpl) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error getting formatter: %s", err)) + return 1 + } + + out, err := f.TransformData(node) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error transform the data: %s", err)) + return 1 + } + c.Ui.Output(out) + return 0 + } + return c.formatNode(client, node) } diff --git a/command/status.go b/command/status.go index dcca9c1bd87..a11ad6e6bce 100644 --- a/command/status.go +++ b/command/status.go @@ -46,6 +46,12 @@ Status Options: -verbose Display full information. + + -format + Display specified format, "json" or "template". + + -t + Sets the template with golang templates format. ` return strings.TrimSpace(helpText) } @@ -56,12 +62,15 @@ func (c *StatusCommand) Synopsis() string { func (c *StatusCommand) Run(args []string) int { var short bool + var format, tmpl string flags := c.Meta.FlagSet("status", FlagSetClient) flags.Usage = func() { c.Ui.Output(c.Help()) } flags.BoolVar(&short, "short", false, "") flags.BoolVar(&c.showEvals, "evals", false, "") flags.BoolVar(&c.verbose, "verbose", false, "") + flags.StringVar(&format, "format", "default", "") + flags.StringVar(&tmpl, "t", "", "") if err := flags.Parse(args); err != nil { return 1 @@ -145,6 +154,23 @@ func (c *StatusCommand) Run(args []string) int { return 1 } + // If output format is specified, format and output the data + if format != "default" { + f, err := DataFormat(format, tmpl) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error getting formatter: %s", err)) + return 1 + } + + out, err := f.TransformData(job) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error transform the data: %s", err)) + return 1 + } + c.Ui.Output(out) + return 0 + } + // Check if it is periodic sJob, err := convertApiJob(job) if err != nil { From 5bd39f8e6591bbe9180e5c953cafafea09bc25d8 Mon Sep 17 00:00:00 2001 From: Kenjiro Nakayama Date: Thu, 4 Aug 2016 18:42:13 +0900 Subject: [PATCH 2/8] fix go panic --- command/data_format.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/command/data_format.go b/command/data_format.go index 1111ca0ceb5..4925629415b 100644 --- a/command/data_format.go +++ b/command/data_format.go @@ -49,8 +49,12 @@ func (p *TemplateFormat) TransformData(data interface{}) (string, error) { return "", fmt.Errorf("template needs to be specified the golang templates.") } - t := template.Must(template.New("format").Parse(p.tmpl)) - err := t.Execute(out, data) + t, err := template.New("format").Parse(p.tmpl) + if err != nil { + return "", err + } + + err = t.Execute(out, data) if err != nil { return "", err } From 02613e170bd3b0348a19319d43aec5b53b401c3e Mon Sep 17 00:00:00 2001 From: Kenjiro Nakayama Date: Thu, 4 Aug 2016 19:19:31 +0900 Subject: [PATCH 3/8] Stop using format option and support json and t option --- command/alloc_status.go | 20 +++++++++++++------- command/data_format.go | 3 +++ command/eval_status.go | 20 +++++++++++++------- command/node_status.go | 20 +++++++++++++------- command/status.go | 26 -------------------------- 5 files changed, 42 insertions(+), 47 deletions(-) diff --git a/command/alloc_status.go b/command/alloc_status.go index 21cc39e6a09..ff9af16a34a 100644 --- a/command/alloc_status.go +++ b/command/alloc_status.go @@ -44,11 +44,11 @@ Alloc Status Options: -verbose Show full information. - -format - Display specified format, "json" or "template". + -json + Display information with json format. -t - Sets the template with golang templates format. + Set golang templates format and display information with it. ` return strings.TrimSpace(helpText) @@ -59,15 +59,15 @@ func (c *AllocStatusCommand) Synopsis() string { } func (c *AllocStatusCommand) Run(args []string) int { - var short, displayStats, verbose bool - var format, tmpl string + var short, displayStats, verbose, json bool + var tmpl string flags := c.Meta.FlagSet("alloc-status", FlagSetClient) flags.Usage = func() { c.Ui.Output(c.Help()) } flags.BoolVar(&short, "short", false, "") flags.BoolVar(&verbose, "verbose", false, "") flags.BoolVar(&displayStats, "stats", false, "") - flags.StringVar(&format, "format", "default", "") + flags.BoolVar(&json, "json", false, "") flags.StringVar(&tmpl, "t", "", "") if err := flags.Parse(args); err != nil { @@ -140,7 +140,13 @@ func (c *AllocStatusCommand) Run(args []string) int { } // If output format is specified, format and output the data - if format != "default" { + var format string + if json { + format = "json" + } else if len(tmpl) > 0 { + format = "template" + } + if len(format) > 0 { f, err := DataFormat(format, tmpl) if err != nil { c.Ui.Error(fmt.Sprintf("Error getting formatter: %s", err)) diff --git a/command/data_format.go b/command/data_format.go index 4925629415b..c11887b9b68 100644 --- a/command/data_format.go +++ b/command/data_format.go @@ -18,6 +18,9 @@ type DataFormatter interface { func DataFormat(format, tmpl string) (DataFormatter, error) { switch format { case "json": + if len(tmpl) > 0 { + return nil, fmt.Errorf("json format does not support template option.") + } return &JSONFormat{}, nil case "template": return &TemplateFormat{tmpl}, nil diff --git a/command/eval_status.go b/command/eval_status.go index ff8becd663f..88799d3fd34 100644 --- a/command/eval_status.go +++ b/command/eval_status.go @@ -32,11 +32,11 @@ Eval Status Options: -verbose Show full information. - -format - Display specified format, "json" or "template". + -json + Display information with json format. -t - Sets the template with golang templates format. + Set golang templates format and display information with it. ` return strings.TrimSpace(helpText) @@ -47,14 +47,14 @@ func (c *EvalStatusCommand) Synopsis() string { } func (c *EvalStatusCommand) Run(args []string) int { - var monitor, verbose bool - var format, tmpl string + var monitor, verbose, json bool + var tmpl string flags := c.Meta.FlagSet("eval-status", FlagSetClient) flags.Usage = func() { c.Ui.Output(c.Help()) } flags.BoolVar(&monitor, "monitor", false, "") flags.BoolVar(&verbose, "verbose", false, "") - flags.StringVar(&format, "format", "default", "") + flags.BoolVar(&json, "json", false, "") flags.StringVar(&tmpl, "t", "", "") if err := flags.Parse(args); err != nil { @@ -134,7 +134,13 @@ func (c *EvalStatusCommand) Run(args []string) int { } // If output format is specified, format and output the data - if format != "default" { + var format string + if json { + format = "json" + } else if len(tmpl) > 0 { + format = "template" + } + if len(format) > 0 { f, err := DataFormat(format, tmpl) if err != nil { c.Ui.Error(fmt.Sprintf("Error getting formatter: %s", err)) diff --git a/command/node_status.go b/command/node_status.go index 274bc8967ce..13d4a154cdb 100644 --- a/command/node_status.go +++ b/command/node_status.go @@ -30,7 +30,7 @@ type NodeStatusCommand struct { list_allocs bool self bool stats bool - format string + json bool tmpl string } @@ -69,11 +69,11 @@ Node Status Options: -verbose Display full information. - -format - Display specified format, "json" or "template". + -json + Display information with json format. -t - Sets the template with golang templates format. + Set golang templates format and display information with it. ` return strings.TrimSpace(helpText) } @@ -91,7 +91,7 @@ func (c *NodeStatusCommand) Run(args []string) int { flags.BoolVar(&c.list_allocs, "allocs", false, "") flags.BoolVar(&c.self, "self", false, "") flags.BoolVar(&c.stats, "stats", false, "") - flags.StringVar(&c.format, "format", "default", "") + flags.BoolVar(&c.json, "json", false, "") flags.StringVar(&c.tmpl, "t", "", "") if err := flags.Parse(args); err != nil { @@ -227,8 +227,14 @@ func (c *NodeStatusCommand) Run(args []string) int { } // If output format is specified, format and output the data - if c.format != "default" { - f, err := DataFormat(c.format, c.tmpl) + var format string + if c.json { + format = "json" + } else if len(c.tmpl) > 0 { + format = "template" + } + if len(format) > 0 { + f, err := DataFormat(format, c.tmpl) if err != nil { c.Ui.Error(fmt.Sprintf("Error getting formatter: %s", err)) return 1 diff --git a/command/status.go b/command/status.go index a11ad6e6bce..dcca9c1bd87 100644 --- a/command/status.go +++ b/command/status.go @@ -46,12 +46,6 @@ Status Options: -verbose Display full information. - - -format - Display specified format, "json" or "template". - - -t - Sets the template with golang templates format. ` return strings.TrimSpace(helpText) } @@ -62,15 +56,12 @@ func (c *StatusCommand) Synopsis() string { func (c *StatusCommand) Run(args []string) int { var short bool - var format, tmpl string flags := c.Meta.FlagSet("status", FlagSetClient) flags.Usage = func() { c.Ui.Output(c.Help()) } flags.BoolVar(&short, "short", false, "") flags.BoolVar(&c.showEvals, "evals", false, "") flags.BoolVar(&c.verbose, "verbose", false, "") - flags.StringVar(&format, "format", "default", "") - flags.StringVar(&tmpl, "t", "", "") if err := flags.Parse(args); err != nil { return 1 @@ -154,23 +145,6 @@ func (c *StatusCommand) Run(args []string) int { return 1 } - // If output format is specified, format and output the data - if format != "default" { - f, err := DataFormat(format, tmpl) - if err != nil { - c.Ui.Error(fmt.Sprintf("Error getting formatter: %s", err)) - return 1 - } - - out, err := f.TransformData(job) - if err != nil { - c.Ui.Error(fmt.Sprintf("Error transform the data: %s", err)) - return 1 - } - c.Ui.Output(out) - return 0 - } - // Check if it is periodic sJob, err := convertApiJob(job) if err != nil { From 8f03eb914bfde8e99934207364dac49bda7e0144 Mon Sep 17 00:00:00 2001 From: Kenjiro Nakayama Date: Sat, 6 Aug 2016 02:51:22 +0900 Subject: [PATCH 4/8] Add doc about -json and -t options --- website/source/docs/commands/alloc-status.html.md.erb | 2 ++ website/source/docs/commands/eval-status.html.md.erb | 4 ++++ website/source/docs/commands/node-status.html.md.erb | 4 ++++ 3 files changed, 10 insertions(+) diff --git a/website/source/docs/commands/alloc-status.html.md.erb b/website/source/docs/commands/alloc-status.html.md.erb index f6051ce43fb..bd475ca8c25 100644 --- a/website/source/docs/commands/alloc-status.html.md.erb +++ b/website/source/docs/commands/alloc-status.html.md.erb @@ -31,6 +31,8 @@ allocations and information will be displayed. * `-short`: Display short output. Shows only the most recent task event. * `-verbose`: Show full information. +* `-json` : Display information with json format. +* `-t` : Set golang templates format and display information with it. ## Examples diff --git a/website/source/docs/commands/eval-status.html.md.erb b/website/source/docs/commands/eval-status.html.md.erb index 7455d6dbff9..e97d45d448c 100644 --- a/website/source/docs/commands/eval-status.html.md.erb +++ b/website/source/docs/commands/eval-status.html.md.erb @@ -48,6 +48,10 @@ indicated by exit code 1. * `-verbose`: Show full information. +* `-json` : Display information with json format. + +* `-t` : Set golang templates format and display information with it. + ## Examples Show the status of an evaluation that has placement failures diff --git a/website/source/docs/commands/node-status.html.md.erb b/website/source/docs/commands/node-status.html.md.erb index 8682a97a9b9..df2d42033df 100644 --- a/website/source/docs/commands/node-status.html.md.erb +++ b/website/source/docs/commands/node-status.html.md.erb @@ -45,6 +45,10 @@ information will be displayed. If running the command on a Nomad Client, the * `-verbose`: Show full information. +* `-json` : Display information with json format. + +* `-t` : Set golang templates format and display information with it. + ## Examples From 4a3f63f64149916c36f4d64edfe6edcb30d359a9 Mon Sep 17 00:00:00 2001 From: Kenjiro Nakayama Date: Sat, 6 Aug 2016 18:54:30 +0900 Subject: [PATCH 5/8] Update help and error message --- command/alloc_status.go | 6 +++--- command/eval_status.go | 6 +++--- command/node_status.go | 6 +++--- website/source/docs/commands/alloc-status.html.md.erb | 4 ++-- website/source/docs/commands/eval-status.html.md.erb | 4 ++-- website/source/docs/commands/node-status.html.md.erb | 4 ++-- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/command/alloc_status.go b/command/alloc_status.go index ff9af16a34a..fb990f6b900 100644 --- a/command/alloc_status.go +++ b/command/alloc_status.go @@ -45,10 +45,10 @@ Alloc Status Options: Show full information. -json - Display information with json format. + Output the allocation in its JSON format. -t - Set golang templates format and display information with it. + Format and display allocation using a Go template. ` return strings.TrimSpace(helpText) @@ -155,7 +155,7 @@ func (c *AllocStatusCommand) Run(args []string) int { out, err := f.TransformData(alloc) if err != nil { - c.Ui.Error(fmt.Sprintf("Error transform the data: %s", err)) + c.Ui.Error(fmt.Sprintf("Error formatting the data: %s", err)) return 1 } c.Ui.Output(out) diff --git a/command/eval_status.go b/command/eval_status.go index 88799d3fd34..ded72454b44 100644 --- a/command/eval_status.go +++ b/command/eval_status.go @@ -33,10 +33,10 @@ Eval Status Options: Show full information. -json - Display information with json format. + Output the evaluation in its JSON format. -t - Set golang templates format and display information with it. + Format and display evaluation using a Go template. ` return strings.TrimSpace(helpText) @@ -149,7 +149,7 @@ func (c *EvalStatusCommand) Run(args []string) int { out, err := f.TransformData(eval) if err != nil { - c.Ui.Error(fmt.Sprintf("Error transform the data: %s", err)) + c.Ui.Error(fmt.Sprintf("Error formatting the data: %s", err)) return 1 } c.Ui.Output(out) diff --git a/command/node_status.go b/command/node_status.go index 13d4a154cdb..2a5c007b4a9 100644 --- a/command/node_status.go +++ b/command/node_status.go @@ -70,10 +70,10 @@ Node Status Options: Display full information. -json - Display information with json format. + Output the node in its JSON format. -t - Set golang templates format and display information with it. + Format and display node using a Go template. ` return strings.TrimSpace(helpText) } @@ -242,7 +242,7 @@ func (c *NodeStatusCommand) Run(args []string) int { out, err := f.TransformData(node) if err != nil { - c.Ui.Error(fmt.Sprintf("Error transform the data: %s", err)) + c.Ui.Error(fmt.Sprintf("Error formatting the data: %s", err)) return 1 } c.Ui.Output(out) diff --git a/website/source/docs/commands/alloc-status.html.md.erb b/website/source/docs/commands/alloc-status.html.md.erb index bd475ca8c25..a42a726bd3d 100644 --- a/website/source/docs/commands/alloc-status.html.md.erb +++ b/website/source/docs/commands/alloc-status.html.md.erb @@ -31,8 +31,8 @@ allocations and information will be displayed. * `-short`: Display short output. Shows only the most recent task event. * `-verbose`: Show full information. -* `-json` : Display information with json format. -* `-t` : Set golang templates format and display information with it. +* `-json` : Output the allocation in its JSON format. +* `-t` : Format and display allocation using a Go template. ## Examples diff --git a/website/source/docs/commands/eval-status.html.md.erb b/website/source/docs/commands/eval-status.html.md.erb index e97d45d448c..74fa5d11d15 100644 --- a/website/source/docs/commands/eval-status.html.md.erb +++ b/website/source/docs/commands/eval-status.html.md.erb @@ -48,9 +48,9 @@ indicated by exit code 1. * `-verbose`: Show full information. -* `-json` : Display information with json format. +* `-json` : Output the evaluation in its JSON format. -* `-t` : Set golang templates format and display information with it. +* `-t` : Format and display evaluation using a Go template. ## Examples diff --git a/website/source/docs/commands/node-status.html.md.erb b/website/source/docs/commands/node-status.html.md.erb index df2d42033df..d556283591a 100644 --- a/website/source/docs/commands/node-status.html.md.erb +++ b/website/source/docs/commands/node-status.html.md.erb @@ -45,9 +45,9 @@ information will be displayed. If running the command on a Nomad Client, the * `-verbose`: Show full information. -* `-json` : Display information with json format. +* `-json` : Output the node in its JSON format. -* `-t` : Set golang templates format and display information with it. +* `-t` : Format and display node using a Go template. ## Examples From 4edc906be4c078503008794e7b8e2c2d1e1736c1 Mon Sep 17 00:00:00 2001 From: Kenjiro Nakayama Date: Sat, 6 Aug 2016 20:30:12 +0900 Subject: [PATCH 6/8] Support JSON and template data output list when no args specified --- command/alloc_status.go | 46 +++++++++++++++++++++++--- command/eval_status.go | 73 ++++++++++++++++++++++++++++++++++++++--- command/inspect.go | 53 ++++++++++++++++++++++++++---- command/node_status.go | 24 ++++++++++++++ 4 files changed, 179 insertions(+), 17 deletions(-) diff --git a/command/alloc_status.go b/command/alloc_status.go index fb990f6b900..55b1563d98e 100644 --- a/command/alloc_status.go +++ b/command/alloc_status.go @@ -76,11 +76,6 @@ func (c *AllocStatusCommand) Run(args []string) int { // Check that we got exactly one allocation ID args = flags.Args() - if len(args) != 1 { - c.Ui.Error(c.Help()) - return 1 - } - allocID := args[0] // Get the HTTP client client, err := c.Meta.Client() @@ -89,6 +84,47 @@ func (c *AllocStatusCommand) Run(args []string) int { return 1 } + // If args not specified but output format is specified, format and output the allocations data list + if len(args) == 0 { + var format string + if json { + format = "json" + } else if len(tmpl) > 0 { + format = "template" + } + if len(format) > 0 { + allocs, _, err := client.Allocations().List(nil) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error querying allocations: %v", err)) + return 1 + } + // Return nothing if no allocations found + if len(allocs) == 0 { + return 0 + } + + f, err := DataFormat(format, tmpl) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error getting formatter: %s", err)) + return 1 + } + + out, err := f.TransformData(allocs) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error formatting the data: %s", err)) + return 1 + } + c.Ui.Output(out) + return 0 + } + } + + if len(args) != 1 { + c.Ui.Error(c.Help()) + return 1 + } + allocID := args[0] + // Truncate the id unless full length is requested length := shortId if verbose { diff --git a/command/eval_status.go b/command/eval_status.go index ded72454b44..36001521365 100644 --- a/command/eval_status.go +++ b/command/eval_status.go @@ -63,11 +63,6 @@ func (c *EvalStatusCommand) Run(args []string) int { // Check that we got exactly one evaluation ID args = flags.Args() - if len(args) != 1 { - c.Ui.Error(c.Help()) - return 1 - } - evalID := args[0] // Get the HTTP client client, err := c.Meta.Client() @@ -76,6 +71,48 @@ func (c *EvalStatusCommand) Run(args []string) int { return 1 } + // If args not specified but output format is specified, format and output the evaluations data list + if len(args) == 0 { + var format string + if json { + format = "json" + } else if len(tmpl) > 0 { + format = "template" + } + if len(format) > 0 { + evals, _, err := client.Evaluations().List(nil) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error querying evaluations: %v", err)) + return 1 + } + // Return nothing if no evaluations found + if len(evals) == 0 { + return 0 + } + + f, err := DataFormat(format, tmpl) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error getting formatter: %s", err)) + return 1 + } + + out, err := f.TransformData(evals) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error formatting the data: %s", err)) + return 1 + } + c.Ui.Output(out) + return 0 + } + } + + if len(args) != 1 { + c.Ui.Error(c.Help()) + return 1 + } + + evalID := args[0] + // Truncate the id unless full length is requested length := shortId if verbose { @@ -102,6 +139,32 @@ func (c *EvalStatusCommand) Run(args []string) int { c.Ui.Error(fmt.Sprintf("No evaluation(s) with prefix or id %q found", evalID)) return 1 } + + if len(args) == 0 { + // If output format is specified, format and output the data + var format string + if json { + format = "json" + } else if len(tmpl) > 0 { + format = "template" + } + if len(format) > 0 { + f, err := DataFormat(format, tmpl) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error getting formatter: %s", err)) + return 1 + } + + out, err := f.TransformData(evals) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error formatting the data: %s", err)) + return 1 + } + c.Ui.Output(out) + return 0 + } + } + if len(evals) > 1 { // Format the evals out := make([]string, len(evals)+1) diff --git a/command/inspect.go b/command/inspect.go index 12c59640074..43f56c86364 100644 --- a/command/inspect.go +++ b/command/inspect.go @@ -30,20 +30,18 @@ func (c *InspectCommand) Synopsis() string { } func (c *InspectCommand) Run(args []string) int { + var ojson bool + var tmpl string + flags := c.Meta.FlagSet("inspect", FlagSetClient) flags.Usage = func() { c.Ui.Output(c.Help()) } + flags.BoolVar(&ojson, "json", false, "") + flags.StringVar(&tmpl, "t", "", "") if err := flags.Parse(args); err != nil { return 1 } - - // Check that we got exactly one job args = flags.Args() - if len(args) != 1 { - c.Ui.Error(c.Help()) - return 1 - } - jobID := args[0] // Get the HTTP client client, err := c.Meta.Client() @@ -52,6 +50,47 @@ func (c *InspectCommand) Run(args []string) int { return 1 } + // If args not specified but output format is specified, format and output the jobs data list + if len(args) == 0 { + var format string + if ojson { + format = "json" + } else if len(tmpl) > 0 { + format = "template" + } + if len(format) > 0 { + jobs, _, err := client.Jobs().List(nil) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error querying jobs: %v", err)) + return 1 + } + f, err := DataFormat(format, tmpl) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error getting formatter: %s", err)) + return 1 + } + // Return nothing if no jobs found + if len(jobs) == 0 { + return 0 + } + + out, err := f.TransformData(jobs) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error formatting the data: %s", err)) + return 1 + } + c.Ui.Output(out) + return 0 + } + } + + // Check that we got exactly one job + if len(args) != 1 { + c.Ui.Error(c.Help()) + return 1 + } + jobID := args[0] + // Check if the job exists jobs, _, err := client.Jobs().PrefixList(jobID) if err != nil { diff --git a/command/node_status.go b/command/node_status.go index 2a5c007b4a9..8fd9cc02ab5 100644 --- a/command/node_status.go +++ b/command/node_status.go @@ -132,6 +132,29 @@ func (c *NodeStatusCommand) Run(args []string) int { return 0 } + // If output format is specified, format and output the node data list + var format string + if c.json { + format = "json" + } else if len(c.tmpl) > 0 { + format = "template" + } + if len(format) > 0 { + f, err := DataFormat(format, c.tmpl) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error getting formatter: %s", err)) + return 1 + } + + out, err := f.TransformData(nodes) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error formatting the data: %s", err)) + return 1 + } + c.Ui.Output(out) + return 0 + } + // Format the nodes list out := make([]string, len(nodes)+1) if c.list_allocs { @@ -139,6 +162,7 @@ func (c *NodeStatusCommand) Run(args []string) int { } else { out[0] = "ID|DC|Name|Class|Drain|Status" } + for i, node := range nodes { if c.list_allocs { numAllocs, err := getRunningAllocs(client, node.ID) From 7d804a41c7607a5769f856ac09c6189e81a91cf7 Mon Sep 17 00:00:00 2001 From: Kenjiro Nakayama Date: Sat, 6 Aug 2016 21:38:41 +0900 Subject: [PATCH 7/8] Add test to check both -json and -t are not specified --- command/alloc_status.go | 10 ++++++++-- command/alloc_status_test.go | 8 ++++++++ command/eval_status.go | 10 ++++++++-- command/eval_status_test.go | 10 ++++++++++ command/inspect.go | 5 ++++- command/inspect_test.go | 9 +++++++++ command/node_status.go | 23 +++++++++++++++-------- command/node_status_test.go | 9 +++++++++ 8 files changed, 71 insertions(+), 13 deletions(-) diff --git a/command/alloc_status.go b/command/alloc_status.go index 55b1563d98e..46f1f18db6b 100644 --- a/command/alloc_status.go +++ b/command/alloc_status.go @@ -87,7 +87,10 @@ func (c *AllocStatusCommand) Run(args []string) int { // If args not specified but output format is specified, format and output the allocations data list if len(args) == 0 { var format string - if json { + if json && len(tmpl) > 0 { + c.Ui.Error("Both -json and -t are not allowed") + return 1 + } else if json { format = "json" } else if len(tmpl) > 0 { format = "template" @@ -177,7 +180,10 @@ func (c *AllocStatusCommand) Run(args []string) int { // If output format is specified, format and output the data var format string - if json { + if json && len(tmpl) > 0 { + c.Ui.Error("Both -json and -t are not allowed") + return 1 + } else if json { format = "json" } else if len(tmpl) > 0 { format = "template" diff --git a/command/alloc_status_test.go b/command/alloc_status_test.go index e8405ae46ee..b284ee15ac1 100644 --- a/command/alloc_status_test.go +++ b/command/alloc_status_test.go @@ -61,5 +61,13 @@ func TestAllocStatusCommand_Fails(t *testing.T) { if out := ui.ErrorWriter.String(); !strings.Contains(out, "No allocation(s) with prefix or id") { t.Fatalf("expected not found error, got: %s", out) } + ui.ErrorWriter.Reset() + // Failed on both -json and -t options are specified + if code := cmd.Run([]string{"-address=" + url, "-json", "-t", "{{.ID}}"}); code != 1 { + t.Fatalf("expected exit 1, got: %d", code) + } + if out := ui.ErrorWriter.String(); !strings.Contains(out, "Both -json and -t are not allowed") { + t.Fatalf("expected getting formatter error, got: %s", out) + } } diff --git a/command/eval_status.go b/command/eval_status.go index 36001521365..5e2125949e6 100644 --- a/command/eval_status.go +++ b/command/eval_status.go @@ -74,7 +74,10 @@ func (c *EvalStatusCommand) Run(args []string) int { // If args not specified but output format is specified, format and output the evaluations data list if len(args) == 0 { var format string - if json { + if json && len(tmpl) > 0 { + c.Ui.Error("Both -json and -t are not allowed") + return 1 + } else if json { format = "json" } else if len(tmpl) > 0 { format = "template" @@ -143,7 +146,10 @@ func (c *EvalStatusCommand) Run(args []string) int { if len(args) == 0 { // If output format is specified, format and output the data var format string - if json { + if json && len(tmpl) > 0 { + c.Ui.Error("Both -json and -t are not allowed") + return 1 + } else if json { format = "json" } else if len(tmpl) > 0 { format = "template" diff --git a/command/eval_status_test.go b/command/eval_status_test.go index 29ea4634f8c..b945cf77046 100644 --- a/command/eval_status_test.go +++ b/command/eval_status_test.go @@ -43,4 +43,14 @@ func TestEvalStatusCommand_Fails(t *testing.T) { if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error querying evaluation") { t.Fatalf("expected failed query error, got: %s", out) } + ui.ErrorWriter.Reset() + + // Failed on both -json and -t options are specified + if code := cmd.Run([]string{"-address=" + url, "-json", "-t", "{{.ID}}"}); code != 1 { + t.Fatalf("expected exit 1, got: %d", code) + } + if out := ui.ErrorWriter.String(); !strings.Contains(out, "Both -json and -t are not allowed") { + t.Fatalf("expected getting formatter error, got: %s", out) + } + } diff --git a/command/inspect.go b/command/inspect.go index 43f56c86364..16659f5920a 100644 --- a/command/inspect.go +++ b/command/inspect.go @@ -53,7 +53,10 @@ func (c *InspectCommand) Run(args []string) int { // If args not specified but output format is specified, format and output the jobs data list if len(args) == 0 { var format string - if ojson { + if ojson && len(tmpl) > 0 { + c.Ui.Error("Both -json and -t are not allowed") + return 1 + } else if ojson { format = "json" } else if len(tmpl) > 0 { format = "template" diff --git a/command/inspect_test.go b/command/inspect_test.go index ba7539abe99..8d16106a349 100644 --- a/command/inspect_test.go +++ b/command/inspect_test.go @@ -43,4 +43,13 @@ func TestInspectCommand_Fails(t *testing.T) { if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error inspecting job") { t.Fatalf("expected failed query error, got: %s", out) } + ui.ErrorWriter.Reset() + + // Failed on both -json and -t options are specified + if code := cmd.Run([]string{"-address=" + url, "-json", "-t", "{{.ID}}"}); code != 1 { + t.Fatalf("expected exit 1, got: %d", code) + } + if out := ui.ErrorWriter.String(); !strings.Contains(out, "Both -json and -t are not allowed") { + t.Fatalf("expected getting formatter error, got: %s", out) + } } diff --git a/command/node_status.go b/command/node_status.go index 8fd9cc02ab5..1c33873a612 100644 --- a/command/node_status.go +++ b/command/node_status.go @@ -120,6 +120,17 @@ func (c *NodeStatusCommand) Run(args []string) int { // Use list mode if no node name was provided if len(args) == 0 && !c.self { + // If output format is specified, format and output the node data list + var format string + if c.json && len(c.tmpl) > 0 { + c.Ui.Error("Both -json and -t are not allowed") + return 1 + } else if c.json { + format = "json" + } else if len(c.tmpl) > 0 { + format = "template" + } + // Query the node info nodes, _, err := client.Nodes().List(nil) if err != nil { @@ -132,13 +143,6 @@ func (c *NodeStatusCommand) Run(args []string) int { return 0 } - // If output format is specified, format and output the node data list - var format string - if c.json { - format = "json" - } else if len(c.tmpl) > 0 { - format = "template" - } if len(format) > 0 { f, err := DataFormat(format, c.tmpl) if err != nil { @@ -252,7 +256,10 @@ func (c *NodeStatusCommand) Run(args []string) int { // If output format is specified, format and output the data var format string - if c.json { + if c.json && len(c.tmpl) > 0 { + c.Ui.Error("Both -json and -t are not allowed") + return 1 + } else if c.json { format = "json" } else if len(c.tmpl) > 0 { format = "template" diff --git a/command/node_status_test.go b/command/node_status_test.go index 604b3625c09..8becbf01247 100644 --- a/command/node_status_test.go +++ b/command/node_status_test.go @@ -200,4 +200,13 @@ func TestNodeStatusCommand_Fails(t *testing.T) { if out := ui.ErrorWriter.String(); !strings.Contains(out, "must contain at least two characters.") { t.Fatalf("expected too few characters error, got: %s", out) } + ui.ErrorWriter.Reset() + + // Failed on both -json and -t options are specified + if code := cmd.Run([]string{"-address=" + url, "-json", "-t", "{{.ID}}"}); code != 1 { + t.Fatalf("expected exit 1, got: %d", code) + } + if out := ui.ErrorWriter.String(); !strings.Contains(out, "Both -json and -t are not allowed") { + t.Fatalf("expected getting formatter error, got: %s", out) + } } From 5aceb8eacf6f160f89f53a3dd1745af879d1a898 Mon Sep 17 00:00:00 2001 From: Kenjiro Nakayama Date: Tue, 9 Aug 2016 22:50:18 +0900 Subject: [PATCH 8/8] Update after another review --- command/data_format_test.go | 23 +++++++++++++ command/eval_status.go | 28 ---------------- command/inspect.go | 33 ++++++++++++++++++- .../source/docs/commands/inspect.html.md.erb | 7 ++++ 4 files changed, 62 insertions(+), 29 deletions(-) diff --git a/command/data_format_test.go b/command/data_format_test.go index 97335505a21..5f4ba50ee34 100644 --- a/command/data_format_test.go +++ b/command/data_format_test.go @@ -1,6 +1,7 @@ package command import ( + "strings" "testing" ) @@ -39,3 +40,25 @@ func TestDataFormat(t *testing.T) { } } } + +func TestInvalidJSONTemplate(t *testing.T) { + // Invalid template {{.foo}} + fm, err := DataFormat("template", "{{.foo}}") + if err != nil { + t.Fatalf("err: %v", err) + } + _, err = fm.TransformData(tData) + if !strings.Contains(err.Error(), "foo is not a field of struct type command.testData") { + t.Fatalf("expected invalid template error, got: %s", err.Error()) + } + + // No template is specified + fm, err = DataFormat("template", "") + if err != nil { + t.Fatalf("err: %v", err) + } + _, err = fm.TransformData(tData) + if !strings.Contains(err.Error(), "template needs to be specified the golang templates.") { + t.Fatalf("expected not specified template error, got: %s", err.Error()) + } +} diff --git a/command/eval_status.go b/command/eval_status.go index 5e2125949e6..0fcde9aa128 100644 --- a/command/eval_status.go +++ b/command/eval_status.go @@ -143,34 +143,6 @@ func (c *EvalStatusCommand) Run(args []string) int { return 1 } - if len(args) == 0 { - // If output format is specified, format and output the data - var format string - if json && len(tmpl) > 0 { - c.Ui.Error("Both -json and -t are not allowed") - return 1 - } else if json { - format = "json" - } else if len(tmpl) > 0 { - format = "template" - } - if len(format) > 0 { - f, err := DataFormat(format, tmpl) - if err != nil { - c.Ui.Error(fmt.Sprintf("Error getting formatter: %s", err)) - return 1 - } - - out, err := f.TransformData(evals) - if err != nil { - c.Ui.Error(fmt.Sprintf("Error formatting the data: %s", err)) - return 1 - } - c.Ui.Output(out) - return 0 - } - } - if len(evals) > 1 { // Format the evals out := make([]string, len(evals)+1) diff --git a/command/inspect.go b/command/inspect.go index 16659f5920a..066f79bbcb8 100644 --- a/command/inspect.go +++ b/command/inspect.go @@ -20,8 +20,16 @@ Usage: nomad inspect [options] General Options: - ` + generalOptionsUsage() + ` + generalOptionsUsage() + ` +Inspect Options: + + -json + Output the evaluation in its JSON format. + + -t + Format and display evaluation using a Go template. +` return strings.TrimSpace(helpText) } @@ -125,6 +133,29 @@ func (c *InspectCommand) Run(args []string) int { return 1 } + // If output format is specified, format and output the data + var format string + if ojson { + format = "json" + } else if len(tmpl) > 0 { + format = "template" + } + if len(format) > 0 { + f, err := DataFormat(format, tmpl) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error getting formatter: %s", err)) + return 1 + } + + out, err := f.TransformData(job) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error formatting the data: %s", err)) + return 1 + } + c.Ui.Output(out) + return 0 + } + // Print the contents of the job req := api.RegisterJobRequest{Job: job} buf, err := json.MarshalIndent(req, "", " ") diff --git a/website/source/docs/commands/inspect.html.md.erb b/website/source/docs/commands/inspect.html.md.erb index 8fc5db6b1ed..e2997400167 100644 --- a/website/source/docs/commands/inspect.html.md.erb +++ b/website/source/docs/commands/inspect.html.md.erb @@ -25,6 +25,13 @@ version of a job Nomad is running. <%= general_options_usage %> +## Inspect Options + +* `-short`: Display short output. Used only when a single node is being queried. + Drops verbose node allocation data from the output. + +* `-verbose`: Show full information. + ## Examples Inspect a submitted job: