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