github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/command/job_stop.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/hashicorp/nomad/api" 8 "github.com/hashicorp/nomad/api/contexts" 9 "github.com/posener/complete" 10 ) 11 12 type JobStopCommand struct { 13 Meta 14 } 15 16 func (c *JobStopCommand) Help() string { 17 helpText := ` 18 Usage: nomad job stop [options] <job> 19 Alias: nomad stop 20 21 Stop an existing job. This command is used to signal allocations to shut 22 down for the given job ID. Upon successful deregistration, an interactive 23 monitor session will start to display log lines as the job unwinds its 24 allocations and completes shutting down. It is safe to exit the monitor 25 early using ctrl+c. 26 27 When ACLs are enabled, this command requires a token with the 'submit-job', 28 'read-job', and 'list-jobs' capabilities for the job's namespace. 29 30 General Options: 31 32 ` + generalOptionsUsage(usageOptsDefault) + ` 33 34 Stop Options: 35 36 -detach 37 Return immediately instead of entering monitor mode. After the 38 deregister command is submitted, a new evaluation ID is printed to the 39 screen, which can be used to examine the evaluation using the eval-status 40 command. 41 42 -eval-priority 43 Override the priority of the evaluations produced as a result of this job 44 deregistration. By default, this is set to the priority of the job. 45 46 -global 47 Stop a multi-region job in all its regions. By default job stop will stop 48 only a single region at a time. Ignored for single-region jobs. 49 50 -no-shutdown-delay 51 Ignore the the group and task shutdown_delay configuration so that there is no 52 delay between service deregistration and task shutdown. Note that using 53 this flag will result in failed network connections to the allocations 54 being stopped. 55 56 -purge 57 Purge is used to stop the job and purge it from the system. If not set, the 58 job will still be queryable and will be purged by the garbage collector. 59 60 -yes 61 Automatic yes to prompts. 62 63 -verbose 64 Display full information. 65 ` 66 return strings.TrimSpace(helpText) 67 } 68 69 func (c *JobStopCommand) Synopsis() string { 70 return "Stop a running job" 71 } 72 73 func (c *JobStopCommand) AutocompleteFlags() complete.Flags { 74 return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient), 75 complete.Flags{ 76 "-detach": complete.PredictNothing, 77 "-eval-priority": complete.PredictNothing, 78 "-purge": complete.PredictNothing, 79 "-global": complete.PredictNothing, 80 "-no-shutdown-delay": complete.PredictNothing, 81 "-yes": complete.PredictNothing, 82 "-verbose": complete.PredictNothing, 83 }) 84 } 85 86 func (c *JobStopCommand) AutocompleteArgs() complete.Predictor { 87 return complete.PredictFunc(func(a complete.Args) []string { 88 client, err := c.Meta.Client() 89 if err != nil { 90 return nil 91 } 92 93 resp, _, err := client.Search().PrefixSearch(a.Last, contexts.Jobs, nil) 94 if err != nil { 95 return []string{} 96 } 97 return resp.Matches[contexts.Jobs] 98 }) 99 } 100 101 func (c *JobStopCommand) Name() string { return "job stop" } 102 103 func (c *JobStopCommand) Run(args []string) int { 104 var detach, purge, verbose, global, autoYes, noShutdownDelay bool 105 var evalPriority int 106 107 flags := c.Meta.FlagSet(c.Name(), FlagSetClient) 108 flags.Usage = func() { c.Ui.Output(c.Help()) } 109 flags.BoolVar(&detach, "detach", false, "") 110 flags.BoolVar(&verbose, "verbose", false, "") 111 flags.BoolVar(&global, "global", false, "") 112 flags.BoolVar(&noShutdownDelay, "no-shutdown-delay", false, "") 113 flags.BoolVar(&autoYes, "yes", false, "") 114 flags.BoolVar(&purge, "purge", false, "") 115 flags.IntVar(&evalPriority, "eval-priority", 0, "") 116 117 if err := flags.Parse(args); err != nil { 118 return 1 119 } 120 121 // Truncate the id unless full length is requested 122 length := shortId 123 if verbose { 124 length = fullId 125 } 126 127 // Check that we got exactly one job 128 args = flags.Args() 129 if len(args) != 1 { 130 c.Ui.Error("This command takes one argument: <job>") 131 c.Ui.Error(commandErrorText(c)) 132 return 1 133 } 134 jobID := strings.TrimSpace(args[0]) 135 136 // Get the HTTP client 137 client, err := c.Meta.Client() 138 if err != nil { 139 c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) 140 return 1 141 } 142 143 // Check if the job exists 144 jobs, _, err := client.Jobs().PrefixList(jobID) 145 if err != nil { 146 c.Ui.Error(fmt.Sprintf("Error deregistering job: %s", err)) 147 return 1 148 } 149 if len(jobs) == 0 { 150 c.Ui.Error(fmt.Sprintf("No job(s) with prefix or id %q found", jobID)) 151 return 1 152 } 153 if len(jobs) > 1 { 154 if (jobID != jobs[0].ID) || (c.allNamespaces() && jobs[0].ID == jobs[1].ID) { 155 c.Ui.Error(fmt.Sprintf("Prefix matched multiple jobs\n\n%s", createStatusListOutput(jobs, c.allNamespaces()))) 156 return 1 157 } 158 } 159 160 // Prefix lookup matched a single job 161 q := &api.QueryOptions{Namespace: jobs[0].JobSummary.Namespace} 162 job, _, err := client.Jobs().Info(jobs[0].ID, q) 163 if err != nil { 164 c.Ui.Error(fmt.Sprintf("Error deregistering job: %s", err)) 165 return 1 166 } 167 168 getConfirmation := func(question string) (int, bool) { 169 answer, err := c.Ui.Ask(question) 170 if err != nil { 171 c.Ui.Error(fmt.Sprintf("Failed to parse answer: %v", err)) 172 return 1, false 173 } 174 175 if answer == "" || strings.ToLower(answer)[0] == 'n' { 176 // No case 177 c.Ui.Output("Cancelling job stop") 178 return 0, false 179 } else if strings.ToLower(answer)[0] == 'y' && len(answer) > 1 { 180 // Non exact match yes 181 c.Ui.Output("For confirmation, an exact ‘y’ is required.") 182 return 0, false 183 } else if answer != "y" { 184 c.Ui.Output("No confirmation detected. For confirmation, an exact 'y' is required.") 185 return 1, false 186 } 187 return 0, true 188 } 189 190 // Confirm the stop if the job was a prefix match 191 if jobID != *job.ID && !autoYes { 192 question := fmt.Sprintf("Are you sure you want to stop job %q? [y/N]", *job.ID) 193 code, confirmed := getConfirmation(question) 194 if !confirmed { 195 return code 196 } 197 } 198 199 // Confirm we want to stop only a single region of a multiregion job 200 if job.IsMultiregion() && !global { 201 question := fmt.Sprintf( 202 "Are you sure you want to stop multi-region job %q in a single region? [y/N]", *job.ID) 203 code, confirmed := getConfirmation(question) 204 if !confirmed { 205 return code 206 } 207 } 208 209 // Invoke the stop 210 opts := &api.DeregisterOptions{Purge: purge, Global: global, EvalPriority: evalPriority, NoShutdownDelay: noShutdownDelay} 211 wq := &api.WriteOptions{Namespace: jobs[0].JobSummary.Namespace} 212 evalID, _, err := client.Jobs().DeregisterOpts(*job.ID, opts, wq) 213 if err != nil { 214 c.Ui.Error(fmt.Sprintf("Error deregistering job: %s", err)) 215 return 1 216 } 217 218 // If we are stopping a periodic job there won't be an evalID. 219 if evalID == "" { 220 return 0 221 } 222 223 if detach { 224 c.Ui.Output(evalID) 225 return 0 226 } 227 228 // Start monitoring the stop eval 229 mon := newMonitor(c.Ui, client, length) 230 return mon.monitor(evalID) 231 }