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