github.com/aminovpavel/nomad@v0.11.8/command/job_run.go (about) 1 package command 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "os" 7 "regexp" 8 "strconv" 9 "strings" 10 "time" 11 12 "github.com/hashicorp/nomad/api" 13 "github.com/hashicorp/nomad/helper" 14 "github.com/posener/complete" 15 ) 16 17 var ( 18 // enforceIndexRegex is a regular expression which extracts the enforcement error 19 enforceIndexRegex = regexp.MustCompile(`\((Enforcing job modify index.*)\)`) 20 ) 21 22 type JobRunCommand struct { 23 Meta 24 JobGetter 25 } 26 27 func (c *JobRunCommand) Help() string { 28 helpText := ` 29 Usage: nomad job run [options] <path> 30 Alias: nomad run 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 job's region is used. 54 55 The run command will set the consul_token of the job based on the following 56 precedence, going from highest to lowest: the -consul-token flag, the 57 $CONSUL_HTTP_TOKEN environment variable and finally the value in the job file. 58 59 The run command will set the vault_token of the job based on the following 60 precedence, going from highest to lowest: the -vault-token flag, the 61 $VAULT_TOKEN environment variable and finally the value in the job file. 62 63 General Options: 64 65 ` + generalOptionsUsage() + ` 66 67 Run Options: 68 69 -check-index 70 If set, the job is only registered or updated if the passed 71 job modify index matches the server side version. If a check-index value of 72 zero is passed, the job is only registered if it does not yet exist. If a 73 non-zero value is passed, it ensures that the job is being updated from a 74 known state. The use of this flag is most common in conjunction with plan 75 command. 76 77 -detach 78 Return immediately instead of entering monitor mode. After job submission, 79 the evaluation ID will be printed to the screen, which can be used to 80 examine the evaluation using the eval-status command. 81 82 -output 83 Output the JSON that would be submitted to the HTTP API without submitting 84 the job. 85 86 -policy-override 87 Sets the flag to force override any soft mandatory Sentinel policies. 88 89 -consul-token 90 If set, the passed Consul token is stored in the job before sending to the 91 Nomad servers. This allows passing the Consul token without storing it in 92 the job file. This overrides the token found in $CONSUL_HTTP_TOKEN environment 93 variable and that found in the job. 94 95 -vault-token 96 If set, the passed Vault token is stored in the job before sending to the 97 Nomad servers. This allows passing the Vault token without storing it in 98 the job file. This overrides the token found in $VAULT_TOKEN environment 99 variable and that found in the job. 100 101 -verbose 102 Display full information. 103 ` 104 return strings.TrimSpace(helpText) 105 } 106 107 func (c *JobRunCommand) Synopsis() string { 108 return "Run a new job or update an existing job" 109 } 110 111 func (c *JobRunCommand) AutocompleteFlags() complete.Flags { 112 return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient), 113 complete.Flags{ 114 "-check-index": complete.PredictNothing, 115 "-detach": complete.PredictNothing, 116 "-verbose": complete.PredictNothing, 117 "-consul-token": complete.PredictNothing, 118 "-vault-token": complete.PredictAnything, 119 "-output": complete.PredictNothing, 120 "-policy-override": complete.PredictNothing, 121 }) 122 } 123 124 func (c *JobRunCommand) AutocompleteArgs() complete.Predictor { 125 return complete.PredictOr(complete.PredictFiles("*.nomad"), complete.PredictFiles("*.hcl")) 126 } 127 128 func (c *JobRunCommand) Name() string { return "job run" } 129 130 func (c *JobRunCommand) Run(args []string) int { 131 var detach, verbose, output, override bool 132 var checkIndexStr, consulToken, vaultToken string 133 134 flags := c.Meta.FlagSet(c.Name(), FlagSetClient) 135 flags.Usage = func() { c.Ui.Output(c.Help()) } 136 flags.BoolVar(&detach, "detach", false, "") 137 flags.BoolVar(&verbose, "verbose", false, "") 138 flags.BoolVar(&output, "output", false, "") 139 flags.BoolVar(&override, "policy-override", false, "") 140 flags.StringVar(&checkIndexStr, "check-index", "", "") 141 flags.StringVar(&consulToken, "consul-token", "", "") 142 flags.StringVar(&vaultToken, "vault-token", "", "") 143 144 if err := flags.Parse(args); err != nil { 145 return 1 146 } 147 148 // Truncate the id unless full length is requested 149 length := shortId 150 if verbose { 151 length = fullId 152 } 153 154 // Check that we got exactly one argument 155 args = flags.Args() 156 if len(args) != 1 { 157 c.Ui.Error("This command takes one argument: <path>") 158 c.Ui.Error(commandErrorText(c)) 159 return 1 160 } 161 162 // Get Job struct from Jobfile 163 job, err := c.JobGetter.ApiJob(args[0]) 164 if err != nil { 165 c.Ui.Error(fmt.Sprintf("Error getting job struct: %s", err)) 166 return 1 167 } 168 169 // Get the HTTP client 170 client, err := c.Meta.Client() 171 if err != nil { 172 c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) 173 return 1 174 } 175 176 // Force the region to be that of the job. 177 if r := job.Region; r != nil { 178 client.SetRegion(*r) 179 } 180 181 // Force the namespace to be that of the job. 182 if n := job.Namespace; n != nil { 183 client.SetNamespace(*n) 184 } 185 186 // Check if the job is periodic or is a parameterized job 187 periodic := job.IsPeriodic() 188 paramjob := job.IsParameterized() 189 190 // Parse the Consul token 191 if consulToken == "" { 192 // Check the environment variable 193 consulToken = os.Getenv("CONSUL_HTTP_TOKEN") 194 } 195 196 if consulToken != "" { 197 job.ConsulToken = helper.StringToPtr(consulToken) 198 } 199 200 // Parse the Vault token 201 if vaultToken == "" { 202 // Check the environment variable 203 vaultToken = os.Getenv("VAULT_TOKEN") 204 } 205 206 if vaultToken != "" { 207 job.VaultToken = helper.StringToPtr(vaultToken) 208 } 209 210 if output { 211 req := api.RegisterJobRequest{Job: job} 212 buf, err := json.MarshalIndent(req, "", " ") 213 if err != nil { 214 c.Ui.Error(fmt.Sprintf("Error converting job: %s", err)) 215 return 1 216 } 217 218 c.Ui.Output(string(buf)) 219 return 0 220 } 221 222 // Parse the check-index 223 checkIndex, enforce, err := parseCheckIndex(checkIndexStr) 224 if err != nil { 225 c.Ui.Error(fmt.Sprintf("Error parsing check-index value %q: %v", checkIndexStr, err)) 226 return 1 227 } 228 229 // Set the register options 230 opts := &api.RegisterOptions{} 231 if enforce { 232 opts.EnforceIndex = true 233 opts.ModifyIndex = checkIndex 234 } 235 if override { 236 opts.PolicyOverride = true 237 } 238 239 // Submit the job 240 resp, _, err := client.Jobs().RegisterOpts(job, opts, nil) 241 if err != nil { 242 if strings.Contains(err.Error(), api.RegisterEnforceIndexErrPrefix) { 243 // Format the error specially if the error is due to index 244 // enforcement 245 matches := enforceIndexRegex.FindStringSubmatch(err.Error()) 246 if len(matches) == 2 { 247 c.Ui.Error(matches[1]) // The matched group 248 c.Ui.Error("Job not updated") 249 return 1 250 } 251 } 252 253 c.Ui.Error(fmt.Sprintf("Error submitting job: %s", err)) 254 return 1 255 } 256 257 // Print any warnings if there are any 258 if resp.Warnings != "" { 259 c.Ui.Output( 260 c.Colorize().Color(fmt.Sprintf("[bold][yellow]Job Warnings:\n%s[reset]\n", resp.Warnings))) 261 } 262 263 evalID := resp.EvalID 264 265 // Check if we should enter monitor mode 266 if detach || periodic || paramjob { 267 c.Ui.Output("Job registration successful") 268 if periodic && !paramjob { 269 loc, err := job.Periodic.GetLocation() 270 if err == nil { 271 now := time.Now().In(loc) 272 next, err := job.Periodic.Next(now) 273 if err != nil { 274 c.Ui.Error(fmt.Sprintf("Error determining next launch time: %v", err)) 275 } else { 276 c.Ui.Output(fmt.Sprintf("Approximate next launch time: %s (%s from now)", 277 formatTime(next), formatTimeDifference(now, next, time.Second))) 278 } 279 } 280 } else if !paramjob { 281 c.Ui.Output("Evaluation ID: " + evalID) 282 } 283 284 return 0 285 } 286 287 // Detach was not specified, so start monitoring 288 mon := newMonitor(c.Ui, client, length) 289 return mon.monitor(evalID, false) 290 291 } 292 293 // parseCheckIndex parses the check-index flag and returns the index, whether it 294 // was set and potentially an error during parsing. 295 func parseCheckIndex(input string) (uint64, bool, error) { 296 if input == "" { 297 return 0, false, nil 298 } 299 300 u, err := strconv.ParseUint(input, 10, 64) 301 return u, true, err 302 }