github.com/jrxfive/nomad@v0.6.1-0.20170802162750-1fef470e89bf/command/run.go (about)

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