From 6c694014b4716ad5debbc8a4dca07a2a75cf3d10 Mon Sep 17 00:00:00 2001 From: Kenjiro Nakayama Date: Sat, 30 Jul 2016 19:20:43 +0900 Subject: [PATCH] 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 ebf83163e..21cc39e6a 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 000000000..1111ca0ce --- /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 000000000..97335505a --- /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 8a87e96bd..ff8becd66 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 36e1ad4df..274bc8967 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 dcca9c1bd..a11ad6e6b 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 {