github.com/nir0s/nomad@v0.8.7-rc1/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 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 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 -output 79 Output the JSON that would be submitted to the HTTP API without submitting 80 the job. 81 82 -policy-override 83 Sets the flag to force override any soft mandatory Sentinel policies. 84 85 -vault-token 86 If set, the passed Vault token is stored in the job before sending to the 87 Nomad servers. This allows passing the Vault token without storing it in 88 the job file. This overrides the token found in $VAULT_TOKEN environment 89 variable and that found in the job. 90 91 -verbose 92 Display full information. 93 ` 94 return strings.TrimSpace(helpText) 95 } 96 97 func (c *JobRunCommand) Synopsis() string { 98 return "Run a new job or update an existing job" 99 } 100 101 func (c *JobRunCommand) AutocompleteFlags() complete.Flags { 102 return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient), 103 complete.Flags{ 104 "-check-index": complete.PredictNothing, 105 "-detach": complete.PredictNothing, 106 "-verbose": complete.PredictNothing, 107 "-vault-token": complete.PredictAnything, 108 "-output": complete.PredictNothing, 109 "-policy-override": complete.PredictNothing, 110 }) 111 } 112 113 func (c *JobRunCommand) AutocompleteArgs() complete.Predictor { 114 return complete.PredictOr(complete.PredictFiles("*.nomad"), complete.PredictFiles("*.hcl")) 115 } 116 117 func (c *JobRunCommand) Name() string { return "job run" } 118 119 func (c *JobRunCommand) Run(args []string) int { 120 var detach, verbose, output, override bool 121 var checkIndexStr, vaultToken string 122 123 flags := c.Meta.FlagSet(c.Name(), FlagSetClient) 124 flags.Usage = func() { c.Ui.Output(c.Help()) } 125 flags.BoolVar(&detach, "detach", false, "") 126 flags.BoolVar(&verbose, "verbose", false, "") 127 flags.BoolVar(&output, "output", false, "") 128 flags.BoolVar(&override, "policy-override", false, "") 129 flags.StringVar(&checkIndexStr, "check-index", "", "") 130 flags.StringVar(&vaultToken, "vault-token", "", "") 131 132 if err := flags.Parse(args); err != nil { 133 return 1 134 } 135 136 // Truncate the id unless full length is requested 137 length := shortId 138 if verbose { 139 length = fullId 140 } 141 142 // Check that we got exactly one argument 143 args = flags.Args() 144 if len(args) != 1 { 145 c.Ui.Error("This command takes one argument: <path>") 146 c.Ui.Error(commandErrorText(c)) 147 return 1 148 } 149 150 // Get Job struct from Jobfile 151 job, err := c.JobGetter.ApiJob(args[0]) 152 if err != nil { 153 c.Ui.Error(fmt.Sprintf("Error getting job struct: %s", err)) 154 return 1 155 } 156 157 // Get the HTTP client 158 client, err := c.Meta.Client() 159 if err != nil { 160 c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) 161 return 1 162 } 163 164 // Force the region to be that of the job. 165 if r := job.Region; r != nil { 166 client.SetRegion(*r) 167 } 168 169 // Force the namespace to be that of the job. 170 if n := job.Namespace; n != nil { 171 client.SetNamespace(*n) 172 } 173 174 // Check if the job is periodic or is a parameterized job 175 periodic := job.IsPeriodic() 176 paramjob := job.IsParameterized() 177 178 // Parse the Vault token 179 if vaultToken == "" { 180 // Check the environment variable 181 vaultToken = os.Getenv("VAULT_TOKEN") 182 } 183 184 if vaultToken != "" { 185 job.VaultToken = helper.StringToPtr(vaultToken) 186 } 187 188 if output { 189 req := api.RegisterJobRequest{Job: job} 190 buf, err := json.MarshalIndent(req, "", " ") 191 if err != nil { 192 c.Ui.Error(fmt.Sprintf("Error converting job: %s", err)) 193 return 1 194 } 195 196 c.Ui.Output(string(buf)) 197 return 0 198 } 199 200 // Parse the check-index 201 checkIndex, enforce, err := parseCheckIndex(checkIndexStr) 202 if err != nil { 203 c.Ui.Error(fmt.Sprintf("Error parsing check-index value %q: %v", checkIndexStr, err)) 204 return 1 205 } 206 207 // Set the register options 208 opts := &api.RegisterOptions{} 209 if enforce { 210 opts.EnforceIndex = true 211 opts.ModifyIndex = checkIndex 212 } 213 if override { 214 opts.PolicyOverride = true 215 } 216 217 // Submit the job 218 resp, _, err := client.Jobs().RegisterOpts(job, opts, nil) 219 if err != nil { 220 if strings.Contains(err.Error(), api.RegisterEnforceIndexErrPrefix) { 221 // Format the error specially if the error is due to index 222 // enforcement 223 matches := enforceIndexRegex.FindStringSubmatch(err.Error()) 224 if len(matches) == 2 { 225 c.Ui.Error(matches[1]) // The matched group 226 c.Ui.Error("Job not updated") 227 return 1 228 } 229 } 230 231 c.Ui.Error(fmt.Sprintf("Error submitting job: %s", err)) 232 return 1 233 } 234 235 // Print any warnings if there are any 236 if resp.Warnings != "" { 237 c.Ui.Output( 238 c.Colorize().Color(fmt.Sprintf("[bold][yellow]Job Warnings:\n%s[reset]\n", resp.Warnings))) 239 } 240 241 evalID := resp.EvalID 242 243 // Check if we should enter monitor mode 244 if detach || periodic || paramjob { 245 c.Ui.Output("Job registration successful") 246 if periodic && !paramjob { 247 loc, err := job.Periodic.GetLocation() 248 if err == nil { 249 now := time.Now().In(loc) 250 next, err := job.Periodic.Next(now) 251 if err != nil { 252 c.Ui.Error(fmt.Sprintf("Error determining next launch time: %v", err)) 253 } else { 254 c.Ui.Output(fmt.Sprintf("Approximate next launch time: %s (%s from now)", 255 formatTime(next), formatTimeDifference(now, next, time.Second))) 256 } 257 } 258 } else if !paramjob { 259 c.Ui.Output("Evaluation ID: " + evalID) 260 } 261 262 return 0 263 } 264 265 // Detach was not specified, so start monitoring 266 mon := newMonitor(c.Ui, client, length) 267 return mon.monitor(evalID, false) 268 269 } 270 271 // parseCheckIndex parses the check-index flag and returns the index, whether it 272 // was set and potentially an error during parsing. 273 func parseCheckIndex(input string) (uint64, bool, error) { 274 if input == "" { 275 return 0, false, nil 276 } 277 278 u, err := strconv.ParseUint(input, 10, 64) 279 return u, true, err 280 }