From d18a3df789ae2ffb862fe3fc15f542478ae0b0af Mon Sep 17 00:00:00 2001 From: Alex Dadgar Date: Fri, 30 Jun 2017 14:27:13 -0700 Subject: [PATCH] fail,pause,resume commands --- command/deployment_fail.go | 105 ++++++++++++++++++++++++++++++ command/deployment_fail_test.go | 34 ++++++++++ command/deployment_pause.go | 87 +++++++++++++++++++++++++ command/deployment_pause_test.go | 34 ++++++++++ command/deployment_resume.go | 103 +++++++++++++++++++++++++++++ command/deployment_resume_test.go | 34 ++++++++++ command/job_revert.go | 2 +- commands.go | 15 +++++ 8 files changed, 413 insertions(+), 1 deletion(-) create mode 100644 command/deployment_fail.go create mode 100644 command/deployment_fail_test.go create mode 100644 command/deployment_pause.go create mode 100644 command/deployment_pause_test.go create mode 100644 command/deployment_resume.go create mode 100644 command/deployment_resume_test.go diff --git a/command/deployment_fail.go b/command/deployment_fail.go new file mode 100644 index 000000000..fdd3c59ba --- /dev/null +++ b/command/deployment_fail.go @@ -0,0 +1,105 @@ +package command + +import ( + "fmt" + "strings" +) + +type DeploymentFailCommand struct { + Meta +} + +func (c *DeploymentFailCommand) Help() string { + helpText := ` +Usage: nomad deployment fail [options] + +Fail is used to mark a deployment as failed. Failing a deployment will +stop the placement of new allocations as part of rolling deployment and +if the job is configured to auto revert, the job will attempt to roll back to a +stable version. + +General Options: + + ` + generalOptionsUsage() + ` + +Fail Options: + + -detach + Return immediately instead of entering monitor mode. After deployment + resume, the evaluation ID will be printed to the screen, which can be used + to examine the evaluation using the eval-status command. + + -verbose + Display full information. +` + return strings.TrimSpace(helpText) +} + +func (c *DeploymentFailCommand) Synopsis() string { + return "Manually fail a deployment" +} + +func (c *DeploymentFailCommand) Run(args []string) int { + var detach, verbose bool + + flags := c.Meta.FlagSet("deployment resume", FlagSetClient) + flags.Usage = func() { c.Ui.Output(c.Help()) } + flags.BoolVar(&detach, "detach", false, "") + flags.BoolVar(&verbose, "verbose", false, "") + + if err := flags.Parse(args); err != nil { + return 1 + } + + // Check that we got no arguments + args = flags.Args() + if l := len(args); l != 1 { + c.Ui.Error(c.Help()) + return 1 + } + + dID := args[0] + + // Truncate the id unless full length is requested + length := shortId + if verbose { + length = fullId + } + + // Get the HTTP client + client, err := c.Meta.Client() + if err != nil { + c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) + return 1 + } + + // Do a prefix lookup + deploy, possible, err := getDeployment(client.Deployments(), dID) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error retrieving deployment: %s", err)) + return 1 + } + + if len(possible) != 0 { + c.Ui.Output(fmt.Sprintf("Prefix matched multiple deployments\n\n%s", formatDeployments(possible, length))) + return 0 + } + + u, _, err := client.Deployments().Fail(deploy.ID, nil) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error failing deployment: %s", err)) + return 1 + } + + c.Ui.Output(fmt.Sprintf("Deployment %q failed", deploy.ID)) + evalCreated := u.EvalID != "" + + // Nothing to do + if detach || !evalCreated { + return 0 + } + + c.Ui.Output("") + mon := newMonitor(c.Ui, client, length) + return mon.monitor(u.EvalID, false) +} diff --git a/command/deployment_fail_test.go b/command/deployment_fail_test.go new file mode 100644 index 000000000..7f734b000 --- /dev/null +++ b/command/deployment_fail_test.go @@ -0,0 +1,34 @@ +package command + +import ( + "strings" + "testing" + + "github.com/mitchellh/cli" +) + +func TestDeploymentFailCommand_Implements(t *testing.T) { + var _ cli.Command = &DeploymentFailCommand{} +} + +func TestDeploymentFailCommand_Fails(t *testing.T) { + ui := new(cli.MockUi) + cmd := &DeploymentFailCommand{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() + + if code := cmd.Run([]string{"-address=nope", "12"}); code != 1 { + t.Fatalf("expected exit code 1, got: %d", code) + } + if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error retrieving deployment") { + t.Fatalf("expected failed query error, got: %s", out) + } + ui.ErrorWriter.Reset() +} diff --git a/command/deployment_pause.go b/command/deployment_pause.go new file mode 100644 index 000000000..838c9e2e6 --- /dev/null +++ b/command/deployment_pause.go @@ -0,0 +1,87 @@ +package command + +import ( + "fmt" + "strings" +) + +type DeploymentPauseCommand struct { + Meta +} + +func (c *DeploymentPauseCommand) Help() string { + helpText := ` +Usage: nomad deployment pause [options] + +Pause is used to pause a deployment. Pausing a deployment will pause the +placement of new allocations as part of rolling deployment. + +General Options: + + ` + generalOptionsUsage() + ` + +Pause Options: + + -verbose + Display full information. +` + return strings.TrimSpace(helpText) +} + +func (c *DeploymentPauseCommand) Synopsis() string { + return "Pause a deployment" +} + +func (c *DeploymentPauseCommand) Run(args []string) int { + var verbose bool + + flags := c.Meta.FlagSet("deployment pause", FlagSetClient) + flags.Usage = func() { c.Ui.Output(c.Help()) } + flags.BoolVar(&verbose, "verbose", false, "") + + if err := flags.Parse(args); err != nil { + return 1 + } + + // Check that we got no arguments + args = flags.Args() + if l := len(args); l != 1 { + c.Ui.Error(c.Help()) + return 1 + } + + dID := args[0] + + // Truncate the id unless full length is requested + length := shortId + if verbose { + length = fullId + } + + // Get the HTTP client + client, err := c.Meta.Client() + if err != nil { + c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) + return 1 + } + + // Do a prefix lookup + deploy, possible, err := getDeployment(client.Deployments(), dID) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error retrieving deployment: %s", err)) + return 1 + } + + if len(possible) != 0 { + c.Ui.Output(fmt.Sprintf("Prefix matched multiple deployments\n\n%s", formatDeployments(possible, length))) + return 0 + } + + if _, _, err := client.Deployments().Pause(deploy.ID, true, nil); err != nil { + c.Ui.Error(fmt.Sprintf("Error pausing deployment: %s", err)) + return 1 + } + + c.Ui.Output(fmt.Sprintf("Deployment %q paused", deploy.ID)) + return 0 +} diff --git a/command/deployment_pause_test.go b/command/deployment_pause_test.go new file mode 100644 index 000000000..279a4a9d2 --- /dev/null +++ b/command/deployment_pause_test.go @@ -0,0 +1,34 @@ +package command + +import ( + "strings" + "testing" + + "github.com/mitchellh/cli" +) + +func TestDeploymentPauseCommand_Implements(t *testing.T) { + var _ cli.Command = &DeploymentPauseCommand{} +} + +func TestDeploymentPauseCommand_Fails(t *testing.T) { + ui := new(cli.MockUi) + cmd := &DeploymentPauseCommand{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() + + if code := cmd.Run([]string{"-address=nope", "12"}); code != 1 { + t.Fatalf("expected exit code 1, got: %d", code) + } + if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error retrieving deployment") { + t.Fatalf("expected failed query error, got: %s", out) + } + ui.ErrorWriter.Reset() +} diff --git a/command/deployment_resume.go b/command/deployment_resume.go new file mode 100644 index 000000000..ff999aa77 --- /dev/null +++ b/command/deployment_resume.go @@ -0,0 +1,103 @@ +package command + +import ( + "fmt" + "strings" +) + +type DeploymentResumeCommand struct { + Meta +} + +func (c *DeploymentResumeCommand) Help() string { + helpText := ` +Usage: nomad deployment resume [options] + +Resume is used to unpause a paused deployment. Resuming a deployment will +resume the placement of new allocations as part of rolling deployment. + +General Options: + + ` + generalOptionsUsage() + ` + +Resume Options: + + -detach + Return immediately instead of entering monitor mode. After deployment + resume, the evaluation ID will be printed to the screen, which can be used + to examine the evaluation using the eval-status command. + + -verbose + Display full information. +` + return strings.TrimSpace(helpText) +} + +func (c *DeploymentResumeCommand) Synopsis() string { + return "Resume a paused deployment" +} + +func (c *DeploymentResumeCommand) Run(args []string) int { + var detach, verbose bool + + flags := c.Meta.FlagSet("deployment resume", FlagSetClient) + flags.Usage = func() { c.Ui.Output(c.Help()) } + flags.BoolVar(&detach, "detach", false, "") + flags.BoolVar(&verbose, "verbose", false, "") + + if err := flags.Parse(args); err != nil { + return 1 + } + + // Check that we got no arguments + args = flags.Args() + if l := len(args); l != 1 { + c.Ui.Error(c.Help()) + return 1 + } + + dID := args[0] + + // Truncate the id unless full length is requested + length := shortId + if verbose { + length = fullId + } + + // Get the HTTP client + client, err := c.Meta.Client() + if err != nil { + c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) + return 1 + } + + // Do a prefix lookup + deploy, possible, err := getDeployment(client.Deployments(), dID) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error retrieving deployment: %s", err)) + return 1 + } + + if len(possible) != 0 { + c.Ui.Output(fmt.Sprintf("Prefix matched multiple deployments\n\n%s", formatDeployments(possible, length))) + return 0 + } + + u, _, err := client.Deployments().Pause(deploy.ID, false, nil) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error resuming deployment: %s", err)) + return 1 + } + + c.Ui.Output(fmt.Sprintf("Deployment %q resumed", deploy.ID)) + evalCreated := u.EvalID != "" + + // Nothing to do + if detach || !evalCreated { + return 0 + } + + c.Ui.Output("") + mon := newMonitor(c.Ui, client, length) + return mon.monitor(u.EvalID, false) +} diff --git a/command/deployment_resume_test.go b/command/deployment_resume_test.go new file mode 100644 index 000000000..b6833bab7 --- /dev/null +++ b/command/deployment_resume_test.go @@ -0,0 +1,34 @@ +package command + +import ( + "strings" + "testing" + + "github.com/mitchellh/cli" +) + +func TestDeploymentResumeCommand_Implements(t *testing.T) { + var _ cli.Command = &DeploymentResumeCommand{} +} + +func TestDeploymentResumeCommand_Fails(t *testing.T) { + ui := new(cli.MockUi) + cmd := &DeploymentResumeCommand{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() + + if code := cmd.Run([]string{"-address=nope", "12"}); code != 1 { + t.Fatalf("expected exit code 1, got: %d", code) + } + if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error retrieving deployment") { + t.Fatalf("expected failed query error, got: %s", out) + } + ui.ErrorWriter.Reset() +} diff --git a/command/job_revert.go b/command/job_revert.go index 9226f36e2..141e33b9b 100644 --- a/command/job_revert.go +++ b/command/job_revert.go @@ -24,7 +24,7 @@ General Options: Revert Options: -detach - Return immediately instead of entering monitor mode. After job dispatch, + Return immediately instead of entering monitor mode. After job revert, the evaluation ID will be printed to the screen, which can be used to examine the evaluation using the eval-status command. diff --git a/commands.go b/commands.go index 5fafe16dc..989a263f7 100644 --- a/commands.go +++ b/commands.go @@ -59,11 +59,26 @@ func Commands(metaPtr *command.Meta) map[string]cli.CommandFactory { Meta: meta, }, nil }, + "deployment fail": func() (cli.Command, error) { + return &command.DeploymentFailCommand{ + Meta: meta, + }, nil + }, "deployment list": func() (cli.Command, error) { return &command.DeploymentListCommand{ Meta: meta, }, nil }, + "deployment pause": func() (cli.Command, error) { + return &command.DeploymentPauseCommand{ + Meta: meta, + }, nil + }, + "deployment resume": func() (cli.Command, error) { + return &command.DeploymentResumeCommand{ + Meta: meta, + }, nil + }, "deployment status": func() (cli.Command, error) { return &command.DeploymentStatusCommand{ Meta: meta,