github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/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    -purge
    43      Purge is used to stop the job and purge it from the system. If not set, the
    44      job will still be queryable and will be purged by the garbage collector.
    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    -yes
    51      Automatic yes to prompts.
    52  
    53    -verbose
    54      Display full information.
    55  `
    56  	return strings.TrimSpace(helpText)
    57  }
    58  
    59  func (c *JobStopCommand) Synopsis() string {
    60  	return "Stop a running job"
    61  }
    62  
    63  func (c *JobStopCommand) AutocompleteFlags() complete.Flags {
    64  	return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
    65  		complete.Flags{
    66  			"-detach":  complete.PredictNothing,
    67  			"-purge":   complete.PredictNothing,
    68  			"-global":  complete.PredictNothing,
    69  			"-yes":     complete.PredictNothing,
    70  			"-verbose": complete.PredictNothing,
    71  		})
    72  }
    73  
    74  func (c *JobStopCommand) AutocompleteArgs() complete.Predictor {
    75  	return complete.PredictFunc(func(a complete.Args) []string {
    76  		client, err := c.Meta.Client()
    77  		if err != nil {
    78  			return nil
    79  		}
    80  
    81  		resp, _, err := client.Search().PrefixSearch(a.Last, contexts.Jobs, nil)
    82  		if err != nil {
    83  			return []string{}
    84  		}
    85  		return resp.Matches[contexts.Jobs]
    86  	})
    87  }
    88  
    89  func (c *JobStopCommand) Name() string { return "job stop" }
    90  
    91  func (c *JobStopCommand) Run(args []string) int {
    92  	var detach, purge, verbose, global, autoYes bool
    93  
    94  	flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
    95  	flags.Usage = func() { c.Ui.Output(c.Help()) }
    96  	flags.BoolVar(&detach, "detach", false, "")
    97  	flags.BoolVar(&verbose, "verbose", false, "")
    98  	flags.BoolVar(&global, "global", false, "")
    99  	flags.BoolVar(&autoYes, "yes", false, "")
   100  	flags.BoolVar(&purge, "purge", false, "")
   101  
   102  	if err := flags.Parse(args); err != nil {
   103  		return 1
   104  	}
   105  
   106  	// Truncate the id unless full length is requested
   107  	length := shortId
   108  	if verbose {
   109  		length = fullId
   110  	}
   111  
   112  	// Check that we got exactly one job
   113  	args = flags.Args()
   114  	if len(args) != 1 {
   115  		c.Ui.Error("This command takes one argument: <job>")
   116  		c.Ui.Error(commandErrorText(c))
   117  		return 1
   118  	}
   119  	jobID := args[0]
   120  
   121  	// Get the HTTP client
   122  	client, err := c.Meta.Client()
   123  	if err != nil {
   124  		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
   125  		return 1
   126  	}
   127  
   128  	// Check if the job exists
   129  	jobs, _, err := client.Jobs().PrefixList(jobID)
   130  	if err != nil {
   131  		c.Ui.Error(fmt.Sprintf("Error deregistering job: %s", err))
   132  		return 1
   133  	}
   134  	if len(jobs) == 0 {
   135  		c.Ui.Error(fmt.Sprintf("No job(s) with prefix or id %q found", jobID))
   136  		return 1
   137  	}
   138  	if len(jobs) > 1 && (c.allNamespaces() || strings.TrimSpace(jobID) != jobs[0].ID) {
   139  		c.Ui.Error(fmt.Sprintf("Prefix matched multiple jobs\n\n%s", createStatusListOutput(jobs, c.allNamespaces())))
   140  		return 1
   141  	}
   142  	// Prefix lookup matched a single job
   143  	q := &api.QueryOptions{Namespace: jobs[0].JobSummary.Namespace}
   144  	job, _, err := client.Jobs().Info(jobs[0].ID, q)
   145  	if err != nil {
   146  		c.Ui.Error(fmt.Sprintf("Error deregistering job: %s", err))
   147  		return 1
   148  	}
   149  
   150  	getConfirmation := func(question string) (int, bool) {
   151  		answer, err := c.Ui.Ask(question)
   152  		if err != nil {
   153  			c.Ui.Error(fmt.Sprintf("Failed to parse answer: %v", err))
   154  			return 1, false
   155  		}
   156  
   157  		if answer == "" || strings.ToLower(answer)[0] == 'n' {
   158  			// No case
   159  			c.Ui.Output("Cancelling job stop")
   160  			return 0, false
   161  		} else if strings.ToLower(answer)[0] == 'y' && len(answer) > 1 {
   162  			// Non exact match yes
   163  			c.Ui.Output("For confirmation, an exact ‘y’ is required.")
   164  			return 0, false
   165  		} else if answer != "y" {
   166  			c.Ui.Output("No confirmation detected. For confirmation, an exact 'y' is required.")
   167  			return 1, false
   168  		}
   169  		return 0, true
   170  	}
   171  
   172  	// Confirm the stop if the job was a prefix match
   173  	if jobID != *job.ID && !autoYes {
   174  		question := fmt.Sprintf("Are you sure you want to stop job %q? [y/N]", *job.ID)
   175  		code, confirmed := getConfirmation(question)
   176  		if !confirmed {
   177  			return code
   178  		}
   179  	}
   180  
   181  	// Confirm we want to stop only a single region of a multiregion job
   182  	if job.IsMultiregion() && !global {
   183  		question := fmt.Sprintf(
   184  			"Are you sure you want to stop multi-region job %q in a single region? [y/N]", *job.ID)
   185  		code, confirmed := getConfirmation(question)
   186  		if !confirmed {
   187  			return code
   188  		}
   189  	}
   190  
   191  	// Invoke the stop
   192  	opts := &api.DeregisterOptions{Purge: purge, Global: global}
   193  	wq := &api.WriteOptions{Namespace: jobs[0].JobSummary.Namespace}
   194  	evalID, _, err := client.Jobs().DeregisterOpts(*job.ID, opts, wq)
   195  	if err != nil {
   196  		c.Ui.Error(fmt.Sprintf("Error deregistering job: %s", err))
   197  		return 1
   198  	}
   199  
   200  	// If we are stopping a periodic job there won't be an evalID.
   201  	if evalID == "" {
   202  		return 0
   203  	}
   204  
   205  	if detach {
   206  		c.Ui.Output(evalID)
   207  		return 0
   208  	}
   209  
   210  	// Start monitoring the stop eval
   211  	mon := newMonitor(c.Ui, client, length)
   212  	return mon.monitor(evalID)
   213  }