github.com/ThomasObenaus/nomad@v0.11.1/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 consul_token of the job based on the following
    56    precedence, going from highest to lowest: the -consul-token flag, the
    57    $CONSUL_HTTP_TOKEN environment variable and finally the value in the job file.
    58  
    59    The run command will set the vault_token of the job based on the following
    60    precedence, going from highest to lowest: the -vault-token flag, the
    61    $VAULT_TOKEN environment variable and finally the value in the job file.
    62  
    63  General Options:
    64  
    65    ` + generalOptionsUsage() + `
    66  
    67  Run Options:
    68  
    69    -check-index
    70      If set, the job is only registered or updated if the passed
    71      job modify index matches the server side version. If a check-index value of
    72      zero is passed, the job is only registered if it does not yet exist. If a
    73      non-zero value is passed, it ensures that the job is being updated from a
    74      known state. The use of this flag is most common in conjunction with plan
    75      command.
    76  
    77    -detach
    78      Return immediately instead of entering monitor mode. After job submission,
    79      the evaluation ID will be printed to the screen, which can be used to
    80      examine the evaluation using the eval-status command.
    81  
    82    -output
    83      Output the JSON that would be submitted to the HTTP API without submitting
    84      the job.
    85  
    86    -policy-override
    87      Sets the flag to force override any soft mandatory Sentinel policies.
    88  
    89    -consul-token
    90      If set, the passed Consul token is stored in the job before sending to the
    91      Nomad servers. This allows passing the Consul token without storing it in
    92      the job file. This overrides the token found in $CONSUL_HTTP_TOKEN environment
    93      variable and that found in the job.
    94  
    95    -vault-token
    96      If set, the passed Vault token is stored in the job before sending to the
    97      Nomad servers. This allows passing the Vault token without storing it in
    98      the job file. This overrides the token found in $VAULT_TOKEN environment
    99      variable and that found in the job.
   100  
   101    -verbose
   102      Display full information.
   103  `
   104  	return strings.TrimSpace(helpText)
   105  }
   106  
   107  func (c *JobRunCommand) Synopsis() string {
   108  	return "Run a new job or update an existing job"
   109  }
   110  
   111  func (c *JobRunCommand) AutocompleteFlags() complete.Flags {
   112  	return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
   113  		complete.Flags{
   114  			"-check-index":     complete.PredictNothing,
   115  			"-detach":          complete.PredictNothing,
   116  			"-verbose":         complete.PredictNothing,
   117  			"-consul-token":    complete.PredictNothing,
   118  			"-vault-token":     complete.PredictAnything,
   119  			"-output":          complete.PredictNothing,
   120  			"-policy-override": complete.PredictNothing,
   121  		})
   122  }
   123  
   124  func (c *JobRunCommand) AutocompleteArgs() complete.Predictor {
   125  	return complete.PredictOr(complete.PredictFiles("*.nomad"), complete.PredictFiles("*.hcl"))
   126  }
   127  
   128  func (c *JobRunCommand) Name() string { return "job run" }
   129  
   130  func (c *JobRunCommand) Run(args []string) int {
   131  	var detach, verbose, output, override bool
   132  	var checkIndexStr, consulToken, vaultToken string
   133  
   134  	flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
   135  	flags.Usage = func() { c.Ui.Output(c.Help()) }
   136  	flags.BoolVar(&detach, "detach", false, "")
   137  	flags.BoolVar(&verbose, "verbose", false, "")
   138  	flags.BoolVar(&output, "output", false, "")
   139  	flags.BoolVar(&override, "policy-override", false, "")
   140  	flags.StringVar(&checkIndexStr, "check-index", "", "")
   141  	flags.StringVar(&consulToken, "consul-token", "", "")
   142  	flags.StringVar(&vaultToken, "vault-token", "", "")
   143  
   144  	if err := flags.Parse(args); err != nil {
   145  		return 1
   146  	}
   147  
   148  	// Truncate the id unless full length is requested
   149  	length := shortId
   150  	if verbose {
   151  		length = fullId
   152  	}
   153  
   154  	// Check that we got exactly one argument
   155  	args = flags.Args()
   156  	if len(args) != 1 {
   157  		c.Ui.Error("This command takes one argument: <path>")
   158  		c.Ui.Error(commandErrorText(c))
   159  		return 1
   160  	}
   161  
   162  	// Get Job struct from Jobfile
   163  	job, err := c.JobGetter.ApiJob(args[0])
   164  	if err != nil {
   165  		c.Ui.Error(fmt.Sprintf("Error getting job struct: %s", err))
   166  		return 1
   167  	}
   168  
   169  	// Get the HTTP client
   170  	client, err := c.Meta.Client()
   171  	if err != nil {
   172  		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
   173  		return 1
   174  	}
   175  
   176  	// Force the region to be that of the job.
   177  	if r := job.Region; r != nil {
   178  		client.SetRegion(*r)
   179  	}
   180  
   181  	// Force the namespace to be that of the job.
   182  	if n := job.Namespace; n != nil {
   183  		client.SetNamespace(*n)
   184  	}
   185  
   186  	// Check if the job is periodic or is a parameterized job
   187  	periodic := job.IsPeriodic()
   188  	paramjob := job.IsParameterized()
   189  
   190  	// Parse the Consul token
   191  	if consulToken == "" {
   192  		// Check the environment variable
   193  		consulToken = os.Getenv("CONSUL_HTTP_TOKEN")
   194  	}
   195  
   196  	if consulToken != "" {
   197  		job.ConsulToken = helper.StringToPtr(consulToken)
   198  	}
   199  
   200  	// Parse the Vault token
   201  	if vaultToken == "" {
   202  		// Check the environment variable
   203  		vaultToken = os.Getenv("VAULT_TOKEN")
   204  	}
   205  
   206  	if vaultToken != "" {
   207  		job.VaultToken = helper.StringToPtr(vaultToken)
   208  	}
   209  
   210  	if output {
   211  		req := api.RegisterJobRequest{Job: job}
   212  		buf, err := json.MarshalIndent(req, "", "    ")
   213  		if err != nil {
   214  			c.Ui.Error(fmt.Sprintf("Error converting job: %s", err))
   215  			return 1
   216  		}
   217  
   218  		c.Ui.Output(string(buf))
   219  		return 0
   220  	}
   221  
   222  	// Parse the check-index
   223  	checkIndex, enforce, err := parseCheckIndex(checkIndexStr)
   224  	if err != nil {
   225  		c.Ui.Error(fmt.Sprintf("Error parsing check-index value %q: %v", checkIndexStr, err))
   226  		return 1
   227  	}
   228  
   229  	// Set the register options
   230  	opts := &api.RegisterOptions{}
   231  	if enforce {
   232  		opts.EnforceIndex = true
   233  		opts.ModifyIndex = checkIndex
   234  	}
   235  	if override {
   236  		opts.PolicyOverride = true
   237  	}
   238  
   239  	// Submit the job
   240  	resp, _, err := client.Jobs().RegisterOpts(job, opts, nil)
   241  	if err != nil {
   242  		if strings.Contains(err.Error(), api.RegisterEnforceIndexErrPrefix) {
   243  			// Format the error specially if the error is due to index
   244  			// enforcement
   245  			matches := enforceIndexRegex.FindStringSubmatch(err.Error())
   246  			if len(matches) == 2 {
   247  				c.Ui.Error(matches[1]) // The matched group
   248  				c.Ui.Error("Job not updated")
   249  				return 1
   250  			}
   251  		}
   252  
   253  		c.Ui.Error(fmt.Sprintf("Error submitting job: %s", err))
   254  		return 1
   255  	}
   256  
   257  	// Print any warnings if there are any
   258  	if resp.Warnings != "" {
   259  		c.Ui.Output(
   260  			c.Colorize().Color(fmt.Sprintf("[bold][yellow]Job Warnings:\n%s[reset]\n", resp.Warnings)))
   261  	}
   262  
   263  	evalID := resp.EvalID
   264  
   265  	// Check if we should enter monitor mode
   266  	if detach || periodic || paramjob {
   267  		c.Ui.Output("Job registration successful")
   268  		if periodic && !paramjob {
   269  			loc, err := job.Periodic.GetLocation()
   270  			if err == nil {
   271  				now := time.Now().In(loc)
   272  				next, err := job.Periodic.Next(now)
   273  				if err != nil {
   274  					c.Ui.Error(fmt.Sprintf("Error determining next launch time: %v", err))
   275  				} else {
   276  					c.Ui.Output(fmt.Sprintf("Approximate next launch time: %s (%s from now)",
   277  						formatTime(next), formatTimeDifference(now, next, time.Second)))
   278  				}
   279  			}
   280  		} else if !paramjob {
   281  			c.Ui.Output("Evaluation ID: " + evalID)
   282  		}
   283  
   284  		return 0
   285  	}
   286  
   287  	// Detach was not specified, so start monitoring
   288  	mon := newMonitor(c.Ui, client, length)
   289  	return mon.monitor(evalID, false)
   290  
   291  }
   292  
   293  // parseCheckIndex parses the check-index flag and returns the index, whether it
   294  // was set and potentially an error during parsing.
   295  func parseCheckIndex(input string) (uint64, bool, error) {
   296  	if input == "" {
   297  		return 0, false, nil
   298  	}
   299  
   300  	u, err := strconv.ParseUint(input, 10, 64)
   301  	return u, true, err
   302  }