1package command
2
3import (
4	"fmt"
5	"strings"
6
7	"github.com/hashicorp/nomad/api/contexts"
8	"github.com/posener/complete"
9)
10
11type JobStopCommand struct {
12	Meta
13}
14
15func (c *JobStopCommand) Help() string {
16	helpText := `
17Usage: nomad job stop [options] <job>
18Alias: nomad stop
19
20  Stop an existing job. This command is used to signal allocations
21  to shut down for the given job ID. Upon successful deregistration,
22  an interactive monitor session will start to display log lines as
23  the job unwinds its allocations and completes shutting down. It
24  is safe to exit the monitor early using ctrl+c.
25
26General Options:
27
28  ` + generalOptionsUsage() + `
29
30Stop Options:
31
32  -detach
33    Return immediately instead of entering monitor mode. After the
34    deregister command is submitted, a new evaluation ID is printed to the
35    screen, which can be used to examine the evaluation using the eval-status
36    command.
37
38  -purge
39    Purge is used to stop the job and purge it from the system. If not set, the
40    job will still be queryable and will be purged by the garbage collector.
41
42  -yes
43    Automatic yes to prompts.
44
45  -verbose
46    Display full information.
47`
48	return strings.TrimSpace(helpText)
49}
50
51func (c *JobStopCommand) Synopsis() string {
52	return "Stop a running job"
53}
54
55func (c *JobStopCommand) AutocompleteFlags() complete.Flags {
56	return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
57		complete.Flags{
58			"-detach":  complete.PredictNothing,
59			"-purge":   complete.PredictNothing,
60			"-yes":     complete.PredictNothing,
61			"-verbose": complete.PredictNothing,
62		})
63}
64
65func (c *JobStopCommand) AutocompleteArgs() complete.Predictor {
66	return complete.PredictFunc(func(a complete.Args) []string {
67		client, err := c.Meta.Client()
68		if err != nil {
69			return nil
70		}
71
72		resp, _, err := client.Search().PrefixSearch(a.Last, contexts.Jobs, nil)
73		if err != nil {
74			return []string{}
75		}
76		return resp.Matches[contexts.Jobs]
77	})
78}
79
80func (c *JobStopCommand) Name() string { return "job stop" }
81
82func (c *JobStopCommand) Run(args []string) int {
83	var detach, purge, verbose, autoYes bool
84
85	flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
86	flags.Usage = func() { c.Ui.Output(c.Help()) }
87	flags.BoolVar(&detach, "detach", false, "")
88	flags.BoolVar(&verbose, "verbose", false, "")
89	flags.BoolVar(&autoYes, "yes", false, "")
90	flags.BoolVar(&purge, "purge", false, "")
91
92	if err := flags.Parse(args); err != nil {
93		return 1
94	}
95
96	// Truncate the id unless full length is requested
97	length := shortId
98	if verbose {
99		length = fullId
100	}
101
102	// Check that we got exactly one job
103	args = flags.Args()
104	if len(args) != 1 {
105		c.Ui.Error("This command takes one argument: <job>")
106		c.Ui.Error(commandErrorText(c))
107		return 1
108	}
109	jobID := args[0]
110
111	// Get the HTTP client
112	client, err := c.Meta.Client()
113	if err != nil {
114		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
115		return 1
116	}
117
118	// Check if the job exists
119	jobs, _, err := client.Jobs().PrefixList(jobID)
120	if err != nil {
121		c.Ui.Error(fmt.Sprintf("Error deregistering job: %s", err))
122		return 1
123	}
124	if len(jobs) == 0 {
125		c.Ui.Error(fmt.Sprintf("No job(s) with prefix or id %q found", jobID))
126		return 1
127	}
128	if len(jobs) > 1 && strings.TrimSpace(jobID) != jobs[0].ID {
129		c.Ui.Error(fmt.Sprintf("Prefix matched multiple jobs\n\n%s", createStatusListOutput(jobs)))
130		return 1
131	}
132	// Prefix lookup matched a single job
133	job, _, err := client.Jobs().Info(jobs[0].ID, nil)
134	if err != nil {
135		c.Ui.Error(fmt.Sprintf("Error deregistering job: %s", err))
136		return 1
137	}
138
139	// Confirm the stop if the job was a prefix match.
140	if jobID != *job.ID && !autoYes {
141		question := fmt.Sprintf("Are you sure you want to stop job %q? [y/N]", *job.ID)
142		answer, err := c.Ui.Ask(question)
143		if err != nil {
144			c.Ui.Error(fmt.Sprintf("Failed to parse answer: %v", err))
145			return 1
146		}
147
148		if answer == "" || strings.ToLower(answer)[0] == 'n' {
149			// No case
150			c.Ui.Output("Cancelling job stop")
151			return 0
152		} else if strings.ToLower(answer)[0] == 'y' && len(answer) > 1 {
153			// Non exact match yes
154			c.Ui.Output("For confirmation, an exact ‘y’ is required.")
155			return 0
156		} else if answer != "y" {
157			c.Ui.Output("No confirmation detected. For confirmation, an exact 'y' is required.")
158			return 1
159		}
160	}
161
162	// Invoke the stop
163	evalID, _, err := client.Jobs().Deregister(*job.ID, purge, nil)
164	if err != nil {
165		c.Ui.Error(fmt.Sprintf("Error deregistering job: %s", err))
166		return 1
167	}
168
169	// If we are stopping a periodic job there won't be an evalID.
170	if evalID == "" {
171		return 0
172	}
173
174	if detach {
175		c.Ui.Output(evalID)
176		return 0
177	}
178
179	// Start monitoring the stop eval
180	mon := newMonitor(c.Ui, client, length)
181	return mon.monitor(evalID, false)
182}
183