github.com/dkerwin/nomad@v0.3.3-0.20160525181927-74554135514b/command/run.go (about)

     1  package command
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/gob"
     6  	"encoding/json"
     7  	"fmt"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/hashicorp/nomad/api"
    12  	"github.com/hashicorp/nomad/jobspec"
    13  	"github.com/hashicorp/nomad/nomad/structs"
    14  )
    15  
    16  type RunCommand struct {
    17  	Meta
    18  }
    19  
    20  func (c *RunCommand) Help() string {
    21  	helpText := `
    22  Usage: nomad run [options] <file>
    23  
    24    Starts running a new job or updates an existing job using
    25    the specification located at <file>. This is the main command
    26    used to interact with Nomad.
    27  
    28    Upon successful job submission, this command will immediately
    29    enter an interactive monitor. This is useful to watch Nomad's
    30    internals make scheduling decisions and place the submitted work
    31    onto nodes. The monitor will end once job placement is done. It
    32    is safe to exit the monitor early using ctrl+c.
    33  
    34    On successful job submission and scheduling, exit code 0 will be
    35    returned. If there are job placement issues encountered
    36    (unsatisfiable constraints, resource exhaustion, etc), then the
    37    exit code will be 2. Any other errors, including client connection
    38    issues or internal errors, are indicated by exit code 1.
    39  
    40  General Options:
    41  
    42    ` + generalOptionsUsage() + `
    43  
    44  Run Options:
    45  
    46    -detach
    47      Return immediately instead of entering monitor mode. After job
    48      submission, the evaluation ID will be printed to the screen.
    49      You can use this ID to start a monitor using the eval-monitor
    50      command later if needed.
    51  
    52    -verbose
    53      Display full information.
    54  
    55    -output
    56      Output the JSON that would be submitted to the HTTP API without submitting
    57      the job.
    58  `
    59  	return strings.TrimSpace(helpText)
    60  }
    61  
    62  func (c *RunCommand) Synopsis() string {
    63  	return "Run a new job or update an existing job"
    64  }
    65  
    66  func (c *RunCommand) Run(args []string) int {
    67  	var detach, verbose, output bool
    68  
    69  	flags := c.Meta.FlagSet("run", FlagSetClient)
    70  	flags.Usage = func() { c.Ui.Output(c.Help()) }
    71  	flags.BoolVar(&detach, "detach", false, "")
    72  	flags.BoolVar(&verbose, "verbose", false, "")
    73  	flags.BoolVar(&output, "output", false, "")
    74  
    75  	if err := flags.Parse(args); err != nil {
    76  		return 1
    77  	}
    78  
    79  	// Truncate the id unless full length is requested
    80  	length := shortId
    81  	if verbose {
    82  		length = fullId
    83  	}
    84  
    85  	// Check that we got exactly one node
    86  	args = flags.Args()
    87  	if len(args) != 1 {
    88  		c.Ui.Error(c.Help())
    89  		return 1
    90  	}
    91  	file := args[0]
    92  
    93  	// Parse the job file
    94  	job, err := jobspec.ParseFile(file)
    95  	if err != nil {
    96  		c.Ui.Error(fmt.Sprintf("Error parsing job file %s: %s", file, err))
    97  		return 1
    98  	}
    99  
   100  	// Initialize any fields that need to be.
   101  	job.InitFields()
   102  
   103  	// Check that the job is valid
   104  	if err := job.Validate(); err != nil {
   105  		c.Ui.Error(fmt.Sprintf("Error validating job: %s", err))
   106  		return 1
   107  	}
   108  
   109  	// Check if the job is periodic.
   110  	periodic := job.IsPeriodic()
   111  
   112  	// Convert it to something we can use
   113  	apiJob, err := convertStructJob(job)
   114  	if err != nil {
   115  		c.Ui.Error(fmt.Sprintf("Error converting job: %s", err))
   116  		return 1
   117  	}
   118  
   119  	if output {
   120  		req := api.RegisterJobRequest{apiJob}
   121  		buf, err := json.MarshalIndent(req, "", "    ")
   122  		if err != nil {
   123  			c.Ui.Error(fmt.Sprintf("Error converting job: %s", err))
   124  			return 1
   125  		}
   126  
   127  		c.Ui.Output(string(buf))
   128  		return 0
   129  	}
   130  
   131  	// Get the HTTP client
   132  	client, err := c.Meta.Client()
   133  	if err != nil {
   134  		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
   135  		return 1
   136  	}
   137  
   138  	// Submit the job
   139  	evalID, _, err := client.Jobs().Register(apiJob, nil)
   140  	if err != nil {
   141  		c.Ui.Error(fmt.Sprintf("Error submitting job: %s", err))
   142  		return 1
   143  	}
   144  
   145  	// Check if we should enter monitor mode
   146  	if detach || periodic {
   147  		c.Ui.Output("Job registration successful")
   148  		if periodic {
   149  			c.Ui.Output(fmt.Sprintf("Approximate next launch time: %v", job.Periodic.Next(time.Now().UTC())))
   150  		} else {
   151  			c.Ui.Output("Evaluation ID: " + evalID)
   152  		}
   153  
   154  		return 0
   155  	}
   156  
   157  	// Detach was not specified, so start monitoring
   158  	mon := newMonitor(c.Ui, client, length)
   159  	return mon.monitor(evalID, false)
   160  
   161  }
   162  
   163  // convertStructJob is used to take a *structs.Job and convert it to an *api.Job.
   164  // This function is just a hammer and probably needs to be revisited.
   165  func convertStructJob(in *structs.Job) (*api.Job, error) {
   166  	gob.Register([]map[string]interface{}{})
   167  	gob.Register([]interface{}{})
   168  	var apiJob *api.Job
   169  	buf := new(bytes.Buffer)
   170  	if err := gob.NewEncoder(buf).Encode(in); err != nil {
   171  		return nil, err
   172  	}
   173  	if err := gob.NewDecoder(buf).Decode(&apiJob); err != nil {
   174  		return nil, err
   175  	}
   176  	return apiJob, nil
   177  }