github.com/blixtra/nomad@v0.7.2-0.20171221000451-da9a1d7bb050/command/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 RunCommand struct { 23 Meta 24 JobGetter 25 } 26 27 func (c *RunCommand) Help() string { 28 helpText := ` 29 Usage: nomad run [options] <path> 30 31 Starts running a new job or updates an existing job using 32 the specification located at <path>. This is the main command 33 used to interact with Nomad. 34 35 If the supplied path is "-", the jobfile is read from stdin. Otherwise 36 it is read from the file at the supplied path or downloaded and 37 read from URL specified. 38 39 Upon successful job submission, this command will immediately 40 enter an interactive monitor. This is useful to watch Nomad's 41 internals make scheduling decisions and place the submitted work 42 onto nodes. The monitor will end once job placement is done. It 43 is safe to exit the monitor early using ctrl+c. 44 45 On successful job submission and scheduling, exit code 0 will be 46 returned. If there are job placement issues encountered 47 (unsatisfiable constraints, resource exhaustion, etc), then the 48 exit code will be 2. Any other errors, including client connection 49 issues or internal errors, are indicated by exit code 1. 50 51 If the job has specified the region, the -region flag and NOMAD_REGION 52 environment variable are overridden and the job's region is used. 53 54 The run command will set the vault_token of the job based on the following 55 precedence, going from highest to lowest: the -vault-token flag, the 56 $VAULT_TOKEN environment variable and finally the value in the job file. 57 58 General Options: 59 60 ` + generalOptionsUsage() + ` 61 62 Run Options: 63 64 -check-index 65 If set, the job is only registered or updated if the passed 66 job modify index matches the server side version. If a check-index value of 67 zero is passed, the job is only registered if it does not yet exist. If a 68 non-zero value is passed, it ensures that the job is being updated from a 69 known state. The use of this flag is most common in conjunction with plan 70 command. 71 72 -detach 73 Return immediately instead of entering monitor mode. After job submission, 74 the evaluation ID will be printed to the screen, which can be used to 75 examine the evaluation using the eval-status command. 76 77 -output 78 Output the JSON that would be submitted to the HTTP API without submitting 79 the job. 80 81 -policy-override 82 Sets the flag to force override any soft mandatory Sentinel policies. 83 84 -vault-token 85 If set, the passed Vault token is stored in the job before sending to the 86 Nomad servers. This allows passing the Vault token without storing it in 87 the job file. This overrides the token found in $VAULT_TOKEN environment 88 variable and that found in the job. 89 90 -verbose 91 Display full information. 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 mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient), 102 complete.Flags{ 103 "-check-index": complete.PredictNothing, 104 "-detach": complete.PredictNothing, 105 "-verbose": complete.PredictNothing, 106 "-vault-token": complete.PredictAnything, 107 "-output": complete.PredictNothing, 108 "-policy-override": complete.PredictNothing, 109 }) 110 } 111 112 func (c *RunCommand) AutocompleteArgs() complete.Predictor { 113 return complete.PredictOr(complete.PredictFiles("*.nomad"), complete.PredictFiles("*.hcl")) 114 } 115 116 func (c *RunCommand) Run(args []string) int { 117 var detach, verbose, output, override bool 118 var checkIndexStr, vaultToken string 119 120 flags := c.Meta.FlagSet("run", FlagSetClient) 121 flags.Usage = func() { c.Ui.Output(c.Help()) } 122 flags.BoolVar(&detach, "detach", false, "") 123 flags.BoolVar(&verbose, "verbose", false, "") 124 flags.BoolVar(&output, "output", false, "") 125 flags.BoolVar(&override, "policy-override", false, "") 126 flags.StringVar(&checkIndexStr, "check-index", "", "") 127 flags.StringVar(&vaultToken, "vault-token", "", "") 128 129 if err := flags.Parse(args); err != nil { 130 return 1 131 } 132 133 // Truncate the id unless full length is requested 134 length := shortId 135 if verbose { 136 length = fullId 137 } 138 139 // Check that we got exactly one argument 140 args = flags.Args() 141 if len(args) != 1 { 142 c.Ui.Error(c.Help()) 143 return 1 144 } 145 146 // Check that we got exactly one node 147 args = flags.Args() 148 if len(args) != 1 { 149 c.Ui.Error(c.Help()) 150 return 1 151 } 152 153 // Get Job struct from Jobfile 154 job, err := c.JobGetter.ApiJob(args[0]) 155 if err != nil { 156 c.Ui.Error(fmt.Sprintf("Error getting job struct: %s", err)) 157 return 1 158 } 159 160 // Get the HTTP client 161 client, err := c.Meta.Client() 162 if err != nil { 163 c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) 164 return 1 165 } 166 167 // Force the region to be that of the job. 168 if r := job.Region; r != nil { 169 client.SetRegion(*r) 170 } 171 172 // Force the namespace to be that of the job. 173 if n := job.Namespace; n != nil { 174 client.SetNamespace(*n) 175 } 176 177 // Check if the job is periodic or is a parameterized job 178 periodic := job.IsPeriodic() 179 paramjob := job.IsParameterized() 180 181 // Parse the Vault token 182 if vaultToken == "" { 183 // Check the environment variable 184 vaultToken = os.Getenv("VAULT_TOKEN") 185 } 186 187 if vaultToken != "" { 188 job.VaultToken = helper.StringToPtr(vaultToken) 189 } 190 191 if output { 192 req := api.RegisterJobRequest{Job: job} 193 buf, err := json.MarshalIndent(req, "", " ") 194 if err != nil { 195 c.Ui.Error(fmt.Sprintf("Error converting job: %s", err)) 196 return 1 197 } 198 199 c.Ui.Output(string(buf)) 200 return 0 201 } 202 203 // Parse the check-index 204 checkIndex, enforce, err := parseCheckIndex(checkIndexStr) 205 if err != nil { 206 c.Ui.Error(fmt.Sprintf("Error parsing check-index value %q: %v", checkIndexStr, err)) 207 return 1 208 } 209 210 // Set the register options 211 opts := &api.RegisterOptions{} 212 if enforce { 213 opts.EnforceIndex = true 214 opts.ModifyIndex = checkIndex 215 } 216 if override { 217 opts.PolicyOverride = true 218 } 219 220 // Submit the job 221 resp, _, err := client.Jobs().RegisterOpts(job, opts, nil) 222 if err != nil { 223 if strings.Contains(err.Error(), api.RegisterEnforceIndexErrPrefix) { 224 // Format the error specially if the error is due to index 225 // enforcement 226 matches := enforceIndexRegex.FindStringSubmatch(err.Error()) 227 if len(matches) == 2 { 228 c.Ui.Error(matches[1]) // The matched group 229 c.Ui.Error("Job not updated") 230 return 1 231 } 232 } 233 234 c.Ui.Error(fmt.Sprintf("Error submitting job: %s", err)) 235 return 1 236 } 237 238 // Print any warnings if there are any 239 if resp.Warnings != "" { 240 c.Ui.Output( 241 c.Colorize().Color(fmt.Sprintf("[bold][yellow]Job Warnings:\n%s[reset]\n", resp.Warnings))) 242 } 243 244 evalID := resp.EvalID 245 246 // Check if we should enter monitor mode 247 if detach || periodic || paramjob { 248 c.Ui.Output("Job registration successful") 249 if periodic && !paramjob { 250 loc, err := job.Periodic.GetLocation() 251 if err == nil { 252 now := time.Now().In(loc) 253 next := job.Periodic.Next(now) 254 c.Ui.Output(fmt.Sprintf("Approximate next launch time: %s (%s from now)", 255 formatTime(next), formatTimeDifference(now, next, time.Second))) 256 } 257 } else if !paramjob { 258 c.Ui.Output("Evaluation ID: " + evalID) 259 } 260 261 return 0 262 } 263 264 // Detach was not specified, so start monitoring 265 mon := newMonitor(c.Ui, client, length) 266 return mon.monitor(evalID, false) 267 268 } 269 270 // parseCheckIndex parses the check-index flag and returns the index, whether it 271 // was set and potentially an error during parsing. 272 func parseCheckIndex(input string) (uint64, bool, error) { 273 if input == "" { 274 return 0, false, nil 275 } 276 277 u, err := strconv.ParseUint(input, 10, 64) 278 return u, true, err 279 }