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  }