github.com/ferranbt/nomad@v0.9.3-0.20190607002617-85c449b7667c/command/job_run.go (about)

     1  package command
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"os"
     7  	"regexp"
     8  	"strconv"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/hashicorp/nomad/api"
    13  	"github.com/hashicorp/nomad/helper"
    14  	"github.com/posener/complete"
    15  )
    16  
    17  var (
    18  	// enforceIndexRegex is a regular expression which extracts the enforcement error
    19  	enforceIndexRegex = regexp.MustCompile(`\((Enforcing job modify index.*)\)`)
    20  )
    21  
    22  type JobRunCommand struct {
    23  	Meta
    24  	JobGetter
    25  }
    26  
    27  func (c *JobRunCommand) Help() string {
    28  	helpText := `
    29  Usage: nomad job run [options] <path>
    30  Alias: nomad run
    31  
    32    Starts running a new job or updates an existing job using
    33    the specification located at <path>. This is the main command
    34    used to interact with Nomad.
    35  
    36    If the supplied path is "-", the jobfile is read from stdin. Otherwise
    37    it is read from the file at the supplied path or downloaded and
    38    read from URL specified.
    39  
    40    Upon successful job submission, this command will immediately
    41    enter an interactive monitor. This is useful to watch Nomad's
    42    internals make scheduling decisions and place the submitted work
    43    onto nodes. The monitor will end once job placement is done. It
    44    is safe to exit the monitor early using ctrl+c.
    45  
    46    On successful job submission and scheduling, exit code 0 will be
    47    returned. If there are job placement issues encountered
    48    (unsatisfiable constraints, resource exhaustion, etc), then the
    49    exit code will be 2. Any other errors, including client connection
    50    issues or internal errors, are indicated by exit code 1.
    51  
    52    If the job has specified the region, the -region flag and NOMAD_REGION
    53    environment variable are overridden and the job's region is used.
    54  
    55    The run command will set the vault_token of the job based on the following
    56    precedence, going from highest to lowest: the -vault-token flag, the
    57    $VAULT_TOKEN environment variable and finally the value in the job file.
    58  
    59  General Options:
    60  
    61    ` + generalOptionsUsage() + `
    62  
    63  Run Options:
    64  
    65    -check-index
    66      If set, the job is only registered or updated if the passed
    67      job modify index matches the server side version. If a check-index value of
    68      zero is passed, the job is only registered if it does not yet exist. If a
    69      non-zero value is passed, it ensures that the job is being updated from a
    70      known state. The use of this flag is most common in conjunction with plan
    71      command.
    72  
    73    -detach
    74      Return immediately instead of entering monitor mode. After job submission,
    75      the evaluation ID will be printed to the screen, which can be used to
    76      examine the evaluation using the eval-status command.
    77  
    78    -output
    79      Output the JSON that would be submitted to the HTTP API without submitting
    80      the job.
    81  
    82    -policy-override
    83      Sets the flag to force override any soft mandatory Sentinel policies.
    84  
    85    -vault-token
    86      If set, the passed Vault token is stored in the job before sending to the
    87      Nomad servers. This allows passing the Vault token without storing it in
    88      the job file. This overrides the token found in $VAULT_TOKEN environment
    89      variable and that found in the job.
    90  
    91    -verbose
    92      Display full information.
    93  `
    94  	return strings.TrimSpace(helpText)
    95  }
    96  
    97  func (c *JobRunCommand) Synopsis() string {
    98  	return "Run a new job or update an existing job"
    99  }
   100  
   101  func (c *JobRunCommand) AutocompleteFlags() complete.Flags {
   102  	return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
   103  		complete.Flags{
   104  			"-check-index":     complete.PredictNothing,
   105  			"-detach":          complete.PredictNothing,
   106  			"-verbose":         complete.PredictNothing,
   107  			"-vault-token":     complete.PredictAnything,
   108  			"-output":          complete.PredictNothing,
   109  			"-policy-override": complete.PredictNothing,
   110  		})
   111  }
   112  
   113  func (c *JobRunCommand) AutocompleteArgs() complete.Predictor {
   114  	return complete.PredictOr(complete.PredictFiles("*.nomad"), complete.PredictFiles("*.hcl"))
   115  }
   116  
   117  func (c *JobRunCommand) Name() string { return "job run" }
   118  
   119  func (c *JobRunCommand) Run(args []string) int {
   120  	var detach, verbose, output, override bool
   121  	var checkIndexStr, vaultToken string
   122  
   123  	flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
   124  	flags.Usage = func() { c.Ui.Output(c.Help()) }
   125  	flags.BoolVar(&detach, "detach", false, "")
   126  	flags.BoolVar(&verbose, "verbose", false, "")
   127  	flags.BoolVar(&output, "output", false, "")
   128  	flags.BoolVar(&override, "policy-override", false, "")
   129  	flags.StringVar(&checkIndexStr, "check-index", "", "")
   130  	flags.StringVar(&vaultToken, "vault-token", "", "")
   131  
   132  	if err := flags.Parse(args); err != nil {
   133  		return 1
   134  	}
   135  
   136  	// Truncate the id unless full length is requested
   137  	length := shortId
   138  	if verbose {
   139  		length = fullId
   140  	}
   141  
   142  	// Check that we got exactly one argument
   143  	args = flags.Args()
   144  	if len(args) != 1 {
   145  		c.Ui.Error("This command takes one argument: <path>")
   146  		c.Ui.Error(commandErrorText(c))
   147  		return 1
   148  	}
   149  
   150  	// Get Job struct from Jobfile
   151  	job, err := c.JobGetter.ApiJob(args[0])
   152  	if err != nil {
   153  		c.Ui.Error(fmt.Sprintf("Error getting job struct: %s", err))
   154  		return 1
   155  	}
   156  
   157  	// Get the HTTP client
   158  	client, err := c.Meta.Client()
   159  	if err != nil {
   160  		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
   161  		return 1
   162  	}
   163  
   164  	// Force the region to be that of the job.
   165  	if r := job.Region; r != nil {
   166  		client.SetRegion(*r)
   167  	}
   168  
   169  	// Force the namespace to be that of the job.
   170  	if n := job.Namespace; n != nil {
   171  		client.SetNamespace(*n)
   172  	}
   173  
   174  	// Check if the job is periodic or is a parameterized job
   175  	periodic := job.IsPeriodic()
   176  	paramjob := job.IsParameterized()
   177  
   178  	// Parse the Vault token
   179  	if vaultToken == "" {
   180  		// Check the environment variable
   181  		vaultToken = os.Getenv("VAULT_TOKEN")
   182  	}
   183  
   184  	if vaultToken != "" {
   185  		job.VaultToken = helper.StringToPtr(vaultToken)
   186  	}
   187  
   188  	if output {
   189  		req := api.RegisterJobRequest{Job: job}
   190  		buf, err := json.MarshalIndent(req, "", "    ")
   191  		if err != nil {
   192  			c.Ui.Error(fmt.Sprintf("Error converting job: %s", err))
   193  			return 1
   194  		}
   195  
   196  		c.Ui.Output(string(buf))
   197  		return 0
   198  	}
   199  
   200  	// Parse the check-index
   201  	checkIndex, enforce, err := parseCheckIndex(checkIndexStr)
   202  	if err != nil {
   203  		c.Ui.Error(fmt.Sprintf("Error parsing check-index value %q: %v", checkIndexStr, err))
   204  		return 1
   205  	}
   206  
   207  	// Set the register options
   208  	opts := &api.RegisterOptions{}
   209  	if enforce {
   210  		opts.EnforceIndex = true
   211  		opts.ModifyIndex = checkIndex
   212  	}
   213  	if override {
   214  		opts.PolicyOverride = true
   215  	}
   216  
   217  	// Submit the job
   218  	resp, _, err := client.Jobs().RegisterOpts(job, opts, nil)
   219  	if err != nil {
   220  		if strings.Contains(err.Error(), api.RegisterEnforceIndexErrPrefix) {
   221  			// Format the error specially if the error is due to index
   222  			// enforcement
   223  			matches := enforceIndexRegex.FindStringSubmatch(err.Error())
   224  			if len(matches) == 2 {
   225  				c.Ui.Error(matches[1]) // The matched group
   226  				c.Ui.Error("Job not updated")
   227  				return 1
   228  			}
   229  		}
   230  
   231  		c.Ui.Error(fmt.Sprintf("Error submitting job: %s", err))
   232  		return 1
   233  	}
   234  
   235  	// Print any warnings if there are any
   236  	if resp.Warnings != "" {
   237  		c.Ui.Output(
   238  			c.Colorize().Color(fmt.Sprintf("[bold][yellow]Job Warnings:\n%s[reset]\n", resp.Warnings)))
   239  	}
   240  
   241  	evalID := resp.EvalID
   242  
   243  	// Check if we should enter monitor mode
   244  	if detach || periodic || paramjob {
   245  		c.Ui.Output("Job registration successful")
   246  		if periodic && !paramjob {
   247  			loc, err := job.Periodic.GetLocation()
   248  			if err == nil {
   249  				now := time.Now().In(loc)
   250  				next, err := job.Periodic.Next(now)
   251  				if err != nil {
   252  					c.Ui.Error(fmt.Sprintf("Error determining next launch time: %v", err))
   253  				} else {
   254  					c.Ui.Output(fmt.Sprintf("Approximate next launch time: %s (%s from now)",
   255  						formatTime(next), formatTimeDifference(now, next, time.Second)))
   256  				}
   257  			}
   258  		} else if !paramjob {
   259  			c.Ui.Output("Evaluation ID: " + evalID)
   260  		}
   261  
   262  		return 0
   263  	}
   264  
   265  	// Detach was not specified, so start monitoring
   266  	mon := newMonitor(c.Ui, client, length)
   267  	return mon.monitor(evalID, false)
   268  
   269  }
   270  
   271  // parseCheckIndex parses the check-index flag and returns the index, whether it
   272  // was set and potentially an error during parsing.
   273  func parseCheckIndex(input string) (uint64, bool, error) {
   274  	if input == "" {
   275  		return 0, false, nil
   276  	}
   277  
   278  	u, err := strconv.ParseUint(input, 10, 64)
   279  	return u, true, err
   280  }