github.com/hernad/nomad@v1.6.112/command/job_dispatch.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package command 5 6 import ( 7 "fmt" 8 "io" 9 "os" 10 "strings" 11 12 "github.com/hernad/nomad/api" 13 flaghelper "github.com/hernad/nomad/helper/flags" 14 "github.com/posener/complete" 15 ) 16 17 type JobDispatchCommand struct { 18 Meta 19 } 20 21 func (c *JobDispatchCommand) Help() string { 22 helpText := ` 23 Usage: nomad job dispatch [options] <parameterized job> [input source] 24 25 Dispatch creates an instance of a parameterized job. A data payload to the 26 dispatched instance can be provided via stdin by using "-" or by specifying a 27 path to a file. Metadata can be supplied by using the meta flag one or more 28 times. 29 30 An optional idempotency token can be used to prevent more than one instance 31 of the job to be dispatched. If an instance with the same token already 32 exists, the command returns without any action. 33 34 Upon successful creation, the dispatched job ID will be printed and the 35 triggered evaluation will be monitored. This can be disabled by supplying the 36 detach flag. 37 38 When ACLs are enabled, this command requires a token with the 'dispatch-job' 39 capability for the job's namespace. The 'list-jobs' capability is required to 40 run the command with a job prefix instead of the exact job ID. The 'read-job' 41 capability is required to monitor the resulting evaluation when -detach is 42 not used. 43 44 General Options: 45 46 ` + generalOptionsUsage(usageOptsDefault) + ` 47 48 Dispatch Options: 49 50 -meta <key>=<value> 51 Meta takes a key/value pair separated by "=". The metadata key will be 52 merged into the job's metadata. The job may define a default value for the 53 key which is overridden when dispatching. The flag can be provided more than 54 once to inject multiple metadata key/value pairs. Arbitrary keys are not 55 allowed. The parameterized job must allow the key to be merged. 56 57 -detach 58 Return immediately instead of entering monitor mode. After job dispatch, 59 the evaluation ID will be printed to the screen, which can be used to 60 examine the evaluation using the eval-status command. 61 62 -idempotency-token 63 Optional identifier used to prevent more than one instance of the job from 64 being dispatched. 65 66 -id-prefix-template 67 Optional prefix template for dispatched job IDs. 68 69 -verbose 70 Display full information. 71 ` 72 return strings.TrimSpace(helpText) 73 } 74 75 func (c *JobDispatchCommand) Synopsis() string { 76 return "Dispatch an instance of a parameterized job" 77 } 78 79 func (c *JobDispatchCommand) AutocompleteFlags() complete.Flags { 80 return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient), 81 complete.Flags{ 82 "-meta": complete.PredictAnything, 83 "-detach": complete.PredictNothing, 84 "-idempotency-token": complete.PredictAnything, 85 "-verbose": complete.PredictNothing, 86 }) 87 } 88 89 func (c *JobDispatchCommand) AutocompleteArgs() complete.Predictor { 90 return complete.PredictFunc(func(a complete.Args) []string { 91 client, err := c.Meta.Client() 92 if err != nil { 93 return nil 94 } 95 96 resp, _, err := client.Jobs().PrefixList(a.Last) 97 if err != nil { 98 return []string{} 99 } 100 101 // filter by parameterized jobs 102 matches := make([]string, 0, len(resp)) 103 for _, job := range resp { 104 if job.ParameterizedJob { 105 matches = append(matches, job.ID) 106 } 107 } 108 return matches 109 110 }) 111 } 112 113 func (c *JobDispatchCommand) Name() string { return "job dispatch" } 114 115 func (c *JobDispatchCommand) Run(args []string) int { 116 var detach, verbose bool 117 var idempotencyToken string 118 var meta []string 119 var idPrefixTemplate string 120 121 flags := c.Meta.FlagSet(c.Name(), FlagSetClient) 122 flags.Usage = func() { c.Ui.Output(c.Help()) } 123 flags.BoolVar(&detach, "detach", false, "") 124 flags.BoolVar(&verbose, "verbose", false, "") 125 flags.StringVar(&idempotencyToken, "idempotency-token", "", "") 126 flags.Var((*flaghelper.StringFlag)(&meta), "meta", "") 127 flags.StringVar(&idPrefixTemplate, "id-prefix-template", "", "") 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 one or two arguments 140 args = flags.Args() 141 if l := len(args); l < 1 || l > 2 { 142 c.Ui.Error("This command takes one or two argument: <parameterized job> [input source]") 143 c.Ui.Error(commandErrorText(c)) 144 return 1 145 } 146 147 var payload []byte 148 var readErr error 149 150 // Read the input 151 if len(args) == 2 { 152 switch args[1] { 153 case "-": 154 payload, readErr = io.ReadAll(os.Stdin) 155 default: 156 payload, readErr = os.ReadFile(args[1]) 157 } 158 if readErr != nil { 159 c.Ui.Error(fmt.Sprintf("Error reading input data: %v", readErr)) 160 return 1 161 } 162 } 163 164 // Build the meta 165 metaMap := make(map[string]string, len(meta)) 166 for _, m := range meta { 167 split := strings.SplitN(m, "=", 2) 168 if len(split) != 2 { 169 c.Ui.Error(fmt.Sprintf("Error parsing meta value: %v", m)) 170 return 1 171 } 172 173 metaMap[split[0]] = split[1] 174 } 175 176 // Get the HTTP client 177 client, err := c.Meta.Client() 178 if err != nil { 179 c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) 180 return 1 181 } 182 183 // Check if the job exists 184 jobIDPrefix := strings.TrimSpace(args[0]) 185 jobID, namespace, err := c.JobIDByPrefix(client, jobIDPrefix, func(j *api.JobListStub) bool { 186 return j.ParameterizedJob 187 }) 188 if err != nil { 189 c.Ui.Error(err.Error()) 190 return 1 191 } 192 193 // Dispatch the job 194 w := &api.WriteOptions{ 195 IdempotencyToken: idempotencyToken, 196 Namespace: namespace, 197 } 198 resp, _, err := client.Jobs().Dispatch(jobID, metaMap, payload, idPrefixTemplate, w) 199 if err != nil { 200 c.Ui.Error(fmt.Sprintf("Failed to dispatch job: %s", err)) 201 return 1 202 } 203 204 // See if an evaluation was created. If the job is periodic there will be no 205 // eval. 206 evalCreated := resp.EvalID != "" 207 208 basic := []string{ 209 fmt.Sprintf("Dispatched Job ID|%s", resp.DispatchedJobID), 210 } 211 if evalCreated { 212 basic = append(basic, fmt.Sprintf("Evaluation ID|%s", limit(resp.EvalID, length))) 213 } 214 c.Ui.Output(formatKV(basic)) 215 216 // Nothing to do 217 if detach || !evalCreated { 218 return 0 219 } 220 221 c.Ui.Output("") 222 mon := newMonitor(c.Ui, client, length) 223 return mon.monitor(resp.EvalID) 224 }