github.com/mattyr/nomad@v0.3.3-0.20160919021406-3485a065154a/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/nomad/structs" 16 ) 17 18 var ( 19 // enforceIndexRegex is a regular expression which extracts the enforcement error 20 enforceIndexRegex = regexp.MustCompile(`\((Enforcing job modify index.*)\)`) 21 ) 22 23 type RunCommand struct { 24 Meta 25 JobGetter 26 } 27 28 func (c *RunCommand) Help() string { 29 helpText := ` 30 Usage: nomad run [options] <path> 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 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 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 -verbose 79 Display full information. 80 81 -vault-token 82 If set, the passed Vault token is stored in the job before sending to the 83 Nomad servers. This allows passing the Vault token without storing it in 84 the job file. This overrides the token found in $VAULT_TOKEN environment 85 variable and that found in the job. 86 87 -output 88 Output the JSON that would be submitted to the HTTP API without submitting 89 the job. 90 ` 91 return strings.TrimSpace(helpText) 92 } 93 94 func (c *RunCommand) Synopsis() string { 95 return "Run a new job or update an existing job" 96 } 97 98 func (c *RunCommand) Run(args []string) int { 99 var detach, verbose, output bool 100 var checkIndexStr, vaultToken string 101 102 flags := c.Meta.FlagSet("run", FlagSetClient) 103 flags.Usage = func() { c.Ui.Output(c.Help()) } 104 flags.BoolVar(&detach, "detach", false, "") 105 flags.BoolVar(&verbose, "verbose", false, "") 106 flags.BoolVar(&output, "output", false, "") 107 flags.StringVar(&checkIndexStr, "check-index", "", "") 108 flags.StringVar(&vaultToken, "vault-token", "", "") 109 110 if err := flags.Parse(args); err != nil { 111 return 1 112 } 113 114 // Truncate the id unless full length is requested 115 length := shortId 116 if verbose { 117 length = fullId 118 } 119 120 // Check that we got exactly one argument 121 args = flags.Args() 122 if len(args) != 1 { 123 c.Ui.Error(c.Help()) 124 return 1 125 } 126 127 // Check that we got exactly one node 128 args = flags.Args() 129 if len(args) != 1 { 130 c.Ui.Error(c.Help()) 131 return 1 132 } 133 134 // Get Job struct from Jobfile 135 job, err := c.JobGetter.StructJob(args[0]) 136 if err != nil { 137 c.Ui.Error(fmt.Sprintf("Error getting job struct: %s", err)) 138 return 1 139 } 140 141 // Initialize any fields that need to be. 142 job.Canonicalize() 143 144 // Check that the job is valid 145 if err := job.Validate(); err != nil { 146 c.Ui.Error(fmt.Sprintf("Error validating job: %v", err)) 147 return 1 148 } 149 150 // Check if the job is periodic. 151 periodic := job.IsPeriodic() 152 153 // Parse the Vault token 154 if vaultToken == "" { 155 // Check the environment variable 156 vaultToken = os.Getenv("VAULT_TOKEN") 157 } 158 159 if vaultToken != "" { 160 job.VaultToken = vaultToken 161 } 162 163 // Convert it to something we can use 164 apiJob, err := convertStructJob(job) 165 if err != nil { 166 c.Ui.Error(fmt.Sprintf("Error converting job: %s", err)) 167 return 1 168 } 169 170 if output { 171 req := api.RegisterJobRequest{Job: apiJob} 172 buf, err := json.MarshalIndent(req, "", " ") 173 if err != nil { 174 c.Ui.Error(fmt.Sprintf("Error converting job: %s", err)) 175 return 1 176 } 177 178 c.Ui.Output(string(buf)) 179 return 0 180 } 181 182 // Get the HTTP client 183 client, err := c.Meta.Client() 184 if err != nil { 185 c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) 186 return 1 187 } 188 189 // Force the region to be that of the job. 190 if r := job.Region; r != "" { 191 client.SetRegion(r) 192 } 193 194 // Parse the check-index 195 checkIndex, enforce, err := parseCheckIndex(checkIndexStr) 196 if err != nil { 197 c.Ui.Error(fmt.Sprintf("Error parsing check-index value %q: %v", checkIndexStr, err)) 198 return 1 199 } 200 201 // Submit the job 202 var evalID string 203 if enforce { 204 evalID, _, err = client.Jobs().EnforceRegister(apiJob, checkIndex, nil) 205 } else { 206 evalID, _, err = client.Jobs().Register(apiJob, nil) 207 } 208 if err != nil { 209 if strings.Contains(err.Error(), api.RegisterEnforceIndexErrPrefix) { 210 // Format the error specially if the error is due to index 211 // enforcement 212 matches := enforceIndexRegex.FindStringSubmatch(err.Error()) 213 if len(matches) == 2 { 214 c.Ui.Error(matches[1]) // The matched group 215 c.Ui.Error("Job not updated") 216 return 1 217 } 218 } 219 220 c.Ui.Error(fmt.Sprintf("Error submitting job: %s", err)) 221 return 1 222 } 223 224 // Check if we should enter monitor mode 225 if detach || periodic { 226 c.Ui.Output("Job registration successful") 227 if periodic { 228 now := time.Now().UTC() 229 next := job.Periodic.Next(now) 230 c.Ui.Output(fmt.Sprintf("Approximate next launch time: %s (%s from now)", 231 formatTime(next), formatTimeDifference(now, next, time.Second))) 232 } else { 233 c.Ui.Output("Evaluation ID: " + evalID) 234 } 235 236 return 0 237 } 238 239 // Detach was not specified, so start monitoring 240 mon := newMonitor(c.Ui, client, length) 241 return mon.monitor(evalID, false) 242 243 } 244 245 // parseCheckIndex parses the check-index flag and returns the index, whether it 246 // was set and potentially an error during parsing. 247 func parseCheckIndex(input string) (uint64, bool, error) { 248 if input == "" { 249 return 0, false, nil 250 } 251 252 u, err := strconv.ParseUint(input, 10, 64) 253 return u, true, err 254 } 255 256 // convertStructJob is used to take a *structs.Job and convert it to an *api.Job. 257 // This function is just a hammer and probably needs to be revisited. 258 func convertStructJob(in *structs.Job) (*api.Job, error) { 259 gob.Register([]map[string]interface{}{}) 260 gob.Register([]interface{}{}) 261 var apiJob *api.Job 262 buf := new(bytes.Buffer) 263 if err := gob.NewEncoder(buf).Encode(in); err != nil { 264 return nil, err 265 } 266 if err := gob.NewDecoder(buf).Decode(&apiJob); err != nil { 267 return nil, err 268 } 269 return apiJob, nil 270 }