From 6532abdf6879d7c64e4e4edde7ed39d432cdcecf Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Tue, 15 Sep 2015 18:22:51 -0700 Subject: [PATCH] command: add primitive run command --- command/run.go | 96 +++++++++++++++++++++++++++++++++++++++++++++ command/run_test.go | 70 +++++++++++++++++++++++++++++++++ commands.go | 6 +++ 3 files changed, 172 insertions(+) create mode 100644 command/run.go create mode 100644 command/run_test.go diff --git a/command/run.go b/command/run.go new file mode 100644 index 000000000..300859946 --- /dev/null +++ b/command/run.go @@ -0,0 +1,96 @@ +package command + +import ( + "bytes" + "encoding/gob" + "fmt" + "strings" + + "github.com/hashicorp/nomad/api" + "github.com/hashicorp/nomad/jobspec" + "github.com/hashicorp/nomad/nomad/structs" +) + +type RunCommand struct { + Meta +} + +func (c *RunCommand) Help() string { + helpText := ` +Usage: nomad run [options] + + Starts running a new job using the definition located at . + This is the main command used to invoke new work in Nomad. + +General Options: + + ` + generalOptionsUsage() + return strings.TrimSpace(helpText) +} + +func (c *RunCommand) Synopsis() string { + return "Run a new job" +} + +func (c *RunCommand) Run(args []string) int { + flags := c.Meta.FlagSet("run", FlagSetClient) + flags.Usage = func() { c.Ui.Output(c.Help()) } + if err := flags.Parse(args); err != nil { + return 1 + } + + // Check that we got exactly one node + args = flags.Args() + if len(args) != 1 { + c.Ui.Error(c.Help()) + return 1 + } + file := args[0] + + // Parse the job file + job, err := jobspec.ParseFile(file) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error parsing job file %s: %s", file, err)) + return 1 + } + + // Convert it to something we can use + apiJob, err := convertJob(job) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error converting job: %s", err)) + return 1 + } + + // Get the HTTP client + client, err := c.Meta.Client() + if err != nil { + c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) + return 1 + } + + // Submit the job + evalID, _, err := client.Jobs().Register(apiJob, nil) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error submitting job: %s", err)) + return 1 + } + + c.Ui.Info("Job registered successfully!\n") + c.Ui.Info("JobID = " + job.ID) + c.Ui.Info("EvalID = " + evalID) + return 0 +} + +// convertJob is used to take a *structs.Job and convert it to an *api.Job. +// This function is just a hammer and probably needs to be revisited. +func convertJob(in *structs.Job) (*api.Job, error) { + var apiJob *api.Job + buf := new(bytes.Buffer) + if err := gob.NewEncoder(buf).Encode(in); err != nil { + return nil, err + } + if err := gob.NewDecoder(buf).Decode(&apiJob); err != nil { + return nil, err + } + return apiJob, nil +} diff --git a/command/run_test.go b/command/run_test.go new file mode 100644 index 000000000..f6a4b212b --- /dev/null +++ b/command/run_test.go @@ -0,0 +1,70 @@ +package command + +import ( + "io/ioutil" + "os" + "strings" + "testing" + + "github.com/mitchellh/cli" +) + +func TestRunCommand_Implements(t *testing.T) { + var _ cli.Command = &RunCommand{} +} + +func TestRunCommand_Fails(t *testing.T) { + ui := new(cli.MockUi) + cmd := &RunCommand{Meta: Meta{Ui: ui}} + + // Fails on misuse + if code := cmd.Run([]string{"some", "bad", "args"}); code != 1 { + t.Fatalf("expected exit code 1, got: %d", code) + } + if out := ui.ErrorWriter.String(); !strings.Contains(out, cmd.Help()) { + t.Fatalf("expected help output, got: %s", out) + } + ui.ErrorWriter.Reset() + + // Fails when specified file does not exist + 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") { + t.Fatalf("expect parsing error, got: %s", out) + } + ui.ErrorWriter.Reset() + + // Fails on invalid job + fh1, err := ioutil.TempFile("", "nomad") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.Remove(fh1.Name()) + if _, err := fh1.WriteString("nope"); err != nil { + t.Fatalf("err: %s", err) + } + if code := cmd.Run([]string{fh1.Name()}); code != 1 { + t.Fatalf("expect exit 1, got: %d", code) + } + if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error parsing") { + t.Fatalf("expect parsing error, got: %s", err) + } + ui.ErrorWriter.Reset() + + // Fails on connection failure + fh2, err := ioutil.TempFile("", "nomad") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.Remove(fh2.Name()) + if _, err := fh2.WriteString(`job "hello" {}`); err != nil { + t.Fatalf("err: %s", err) + } + if code := cmd.Run([]string{"-address=nope", fh2.Name()}); code != 1 { + t.Fatalf("expected exit code 1, got: %d", code) + } + if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error submitting job") { + t.Fatalf("expected failed query error, got: %s", out) + } +} diff --git a/commands.go b/commands.go index e4f347382..3916d6eed 100644 --- a/commands.go +++ b/commands.go @@ -70,6 +70,12 @@ func Commands(metaPtr *command.Meta) map[string]cli.CommandFactory { }, nil }, + "run": func() (cli.Command, error) { + return &command.RunCommand{ + Meta: meta, + }, nil + }, + "spawn-daemon": func() (cli.Command, error) { return &command.SpawnDaemonCommand{ Meta: meta,