From df302a2c5953fe56a2769230e8d318c051a1e1eb Mon Sep 17 00:00:00 2001 From: Kenjiro Nakayama Date: Fri, 22 Jul 2016 11:59:55 +0900 Subject: [PATCH] Support nomad plan to take jobfile from stdin --- command/plan.go | 39 +++++++++++++++++++++++++++++++++----- command/plan_test.go | 45 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 6 deletions(-) diff --git a/command/plan.go b/command/plan.go index f66018b82..aa85aceb0 100644 --- a/command/plan.go +++ b/command/plan.go @@ -2,6 +2,8 @@ package command import ( "fmt" + "io" + "os" "sort" "strings" "time" @@ -26,6 +28,9 @@ potentially invalid.` type PlanCommand struct { Meta color *colorstring.Colorize + + // The fields below can be overwritten for tests + testStdin io.Reader } func (c *PlanCommand) Help() string { @@ -37,6 +42,9 @@ Usage: nomad plan [options] changes to the cluster but gives insight into whether the job could be run successfully and how it would affect existing allocations. + If the supplied path is "-", the jobfile is read from stdin. Otherwise + it is read from the file at the supplied path. + A job modify index is returned with the plan. This value can be used when submitting the job using "nomad run -check-index", which will check that the job was not modified between the plan and run command before invoking the @@ -86,12 +94,33 @@ func (c *PlanCommand) Run(args []string) int { c.Ui.Error(c.Help()) return 1 } - file := args[0] - // Parse the job file - job, err := jobspec.ParseFile(file) + // Read the Jobfile + path := args[0] + + var f io.Reader + switch path { + case "-": + if c.testStdin != nil { + f = c.testStdin + } else { + f = os.Stdin + } + path = "stdin" + default: + file, err := os.Open(path) + defer file.Close() + if err != nil { + c.Ui.Error(fmt.Sprintf("Error opening file %q: %v", path, err)) + return 1 + } + f = file + } + + // Parse the JobFile + job, err := jobspec.Parse(f) if err != nil { - c.Ui.Error(fmt.Sprintf("Error parsing job file %s: %s", file, err)) + c.Ui.Error(fmt.Sprintf("Error parsing job file %s: %v", path, err)) return 1 } @@ -142,7 +171,7 @@ func (c *PlanCommand) Run(args []string) int { c.Ui.Output("") // Print the job index info - c.Ui.Output(c.Colorize().Color(formatJobModifyIndex(resp.JobModifyIndex, file))) + c.Ui.Output(c.Colorize().Color(formatJobModifyIndex(resp.JobModifyIndex, path))) return 0 } diff --git a/command/plan_test.go b/command/plan_test.go index af53826d5..2187ac931 100644 --- a/command/plan_test.go +++ b/command/plan_test.go @@ -30,7 +30,7 @@ func TestPlanCommand_Fails(t *testing.T) { if code := cmd.Run([]string{"/unicorns/leprechauns"}); code != 1 { t.Fatalf("expect exit 1, got: %d", code) } - if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error parsing") { + if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error opening") { t.Fatalf("expect parsing error, got: %s", out) } ui.ErrorWriter.Reset() @@ -101,3 +101,46 @@ job "job1" { t.Fatalf("expected failed query error, got: %s", out) } } + +func TestPlanCommand_From_STDIN(t *testing.T) { + stdinR, stdinW, err := os.Pipe() + if err != nil { + t.Fatalf("err: %s", err) + } + + ui := new(cli.MockUi) + cmd := &PlanCommand{ + Meta: Meta{Ui: ui}, + testStdin: stdinR, + } + + go func() { + stdinW.WriteString(` +job "job1" { + type = "service" + datacenters = [ "dc1" ] + group "group1" { + count = 1 + task "task1" { + driver = "exec" + resources = { + cpu = 1000 + disk = 150 + memory = 512 + } + } + } +}`) + stdinW.Close() + }() + + args := []string{"-"} + if code := cmd.Run(args); code != 1 { + t.Fatalf("expected exit code 1, got %d: %q", code, ui.ErrorWriter.String()) + } + + if out := ui.ErrorWriter.String(); !strings.Contains(out, "connection refused") { + t.Fatalf("expected runtime error, got: %s", out) + } + ui.ErrorWriter.Reset() +}