github.com/djenriquez/nomad-1@v0.8.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 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) Run(args []string) int {
   118  	var detach, verbose, output, override bool
   119  	var checkIndexStr, vaultToken string
   120  
   121  	flags := c.Meta.FlagSet("job run", FlagSetClient)
   122  	flags.Usage = func() { c.Ui.Output(c.Help()) }
   123  	flags.BoolVar(&detach, "detach", false, "")
   124  	flags.BoolVar(&verbose, "verbose", false, "")
   125  	flags.BoolVar(&output, "output", false, "")
   126  	flags.BoolVar(&override, "policy-override", false, "")
   127  	flags.StringVar(&checkIndexStr, "check-index", "", "")
   128  	flags.StringVar(&vaultToken, "vault-token", "", "")
   129  
   130  	if err := flags.Parse(args); err != nil {
   131  		return 1
   132  	}
   133  
   134  	// Truncate the id unless full length is requested
   135  	length := shortId
   136  	if verbose {
   137  		length = fullId
   138  	}
   139  
   140  	// Check that we got exactly one argument
   141  	args = flags.Args()
   142  	if len(args) != 1 {
   143  		c.Ui.Error(c.Help())
   144  		return 1
   145  	}
   146  
   147  	// Check that we got exactly one node
   148  	args = flags.Args()
   149  	if len(args) != 1 {
   150  		c.Ui.Error(c.Help())
   151  		return 1
   152  	}
   153  
   154  	// Get Job struct from Jobfile
   155  	job, err := c.JobGetter.ApiJob(args[0])
   156  	if err != nil {
   157  		c.Ui.Error(fmt.Sprintf("Error getting job struct: %s", err))
   158  		return 1
   159  	}
   160  
   161  	// Get the HTTP client
   162  	client, err := c.Meta.Client()
   163  	if err != nil {
   164  		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
   165  		return 1
   166  	}
   167  
   168  	// Force the region to be that of the job.
   169  	if r := job.Region; r != nil {
   170  		client.SetRegion(*r)
   171  	}
   172  
   173  	// Force the namespace to be that of the job.
   174  	if n := job.Namespace; n != nil {
   175  		client.SetNamespace(*n)
   176  	}
   177  
   178  	// Check if the job is periodic or is a parameterized job
   179  	periodic := job.IsPeriodic()
   180  	paramjob := job.IsParameterized()
   181  
   182  	// Parse the Vault token
   183  	if vaultToken == "" {
   184  		// Check the environment variable
   185  		vaultToken = os.Getenv("VAULT_TOKEN")
   186  	}
   187  
   188  	if vaultToken != "" {
   189  		job.VaultToken = helper.StringToPtr(vaultToken)
   190  	}
   191  
   192  	if output {
   193  		req := api.RegisterJobRequest{Job: job}
   194  		buf, err := json.MarshalIndent(req, "", "    ")
   195  		if err != nil {
   196  			c.Ui.Error(fmt.Sprintf("Error converting job: %s", err))
   197  			return 1
   198  		}
   199  
   200  		c.Ui.Output(string(buf))
   201  		return 0
   202  	}
   203  
   204  	// Parse the check-index
   205  	checkIndex, enforce, err := parseCheckIndex(checkIndexStr)
   206  	if err != nil {
   207  		c.Ui.Error(fmt.Sprintf("Error parsing check-index value %q: %v", checkIndexStr, err))
   208  		return 1
   209  	}
   210  
   211  	// Set the register options
   212  	opts := &api.RegisterOptions{}
   213  	if enforce {
   214  		opts.EnforceIndex = true
   215  		opts.ModifyIndex = checkIndex
   216  	}
   217  	if override {
   218  		opts.PolicyOverride = true
   219  	}
   220  
   221  	// Submit the job
   222  	resp, _, err := client.Jobs().RegisterOpts(job, opts, nil)
   223  	if err != nil {
   224  		if strings.Contains(err.Error(), api.RegisterEnforceIndexErrPrefix) {
   225  			// Format the error specially if the error is due to index
   226  			// enforcement
   227  			matches := enforceIndexRegex.FindStringSubmatch(err.Error())
   228  			if len(matches) == 2 {
   229  				c.Ui.Error(matches[1]) // The matched group
   230  				c.Ui.Error("Job not updated")
   231  				return 1
   232  			}
   233  		}
   234  
   235  		c.Ui.Error(fmt.Sprintf("Error submitting job: %s", err))
   236  		return 1
   237  	}
   238  
   239  	// Print any warnings if there are any
   240  	if resp.Warnings != "" {
   241  		c.Ui.Output(
   242  			c.Colorize().Color(fmt.Sprintf("[bold][yellow]Job Warnings:\n%s[reset]\n", resp.Warnings)))
   243  	}
   244  
   245  	evalID := resp.EvalID
   246  
   247  	// Check if we should enter monitor mode
   248  	if detach || periodic || paramjob {
   249  		c.Ui.Output("Job registration successful")
   250  		if periodic && !paramjob {
   251  			loc, err := job.Periodic.GetLocation()
   252  			if err == nil {
   253  				now := time.Now().In(loc)
   254  				next := job.Periodic.Next(now)
   255  				c.Ui.Output(fmt.Sprintf("Approximate next launch time: %s (%s from now)",
   256  					formatTime(next), formatTimeDifference(now, next, time.Second)))
   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  }