github.com/hhrutter/nomad@v0.6.0-rc2.0.20170723054333-80c4b03f0705/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 }