1package command
2
3import (
4	"fmt"
5	"io/ioutil"
6	"os"
7	"strings"
8
9	"github.com/hashicorp/nomad/api/contexts"
10	flaghelper "github.com/hashicorp/nomad/helper/flag-helpers"
11	"github.com/posener/complete"
12)
13
14type JobDispatchCommand struct {
15	Meta
16}
17
18func (c *JobDispatchCommand) Help() string {
19	helpText := `
20Usage: nomad job dispatch [options] <parameterized job> [input source]
21
22  Dispatch creates an instance of a parameterized job. A data payload to the
23  dispatched instance can be provided via stdin by using "-" or by specifying a
24  path to a file. Metadata can be supplied by using the meta flag one or more
25  times.
26
27  Upon successful creation, the dispatched job ID will be printed and the
28  triggered evaluation will be monitored. This can be disabled by supplying the
29  detach flag.
30
31General Options:
32
33  ` + generalOptionsUsage() + `
34
35Dispatch Options:
36
37  -meta <key>=<value>
38    Meta takes a key/value pair separated by "=". The metadata key will be
39    merged into the job's metadata. The job may define a default value for the
40    key which is overridden when dispatching. The flag can be provided more than
41    once to inject multiple metadata key/value pairs. Arbitrary keys are not
42    allowed. The parameterized job must allow the key to be merged.
43
44  -detach
45    Return immediately instead of entering monitor mode. After job dispatch,
46    the evaluation ID will be printed to the screen, which can be used to
47    examine the evaluation using the eval-status command.
48
49  -verbose
50    Display full information.
51`
52	return strings.TrimSpace(helpText)
53}
54
55func (c *JobDispatchCommand) Synopsis() string {
56	return "Dispatch an instance of a parameterized job"
57}
58
59func (c *JobDispatchCommand) AutocompleteFlags() complete.Flags {
60	return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
61		complete.Flags{
62			"-meta":    complete.PredictAnything,
63			"-detach":  complete.PredictNothing,
64			"-verbose": complete.PredictNothing,
65		})
66}
67
68func (c *JobDispatchCommand) AutocompleteArgs() complete.Predictor {
69	return complete.PredictFunc(func(a complete.Args) []string {
70		client, err := c.Meta.Client()
71		if err != nil {
72			return nil
73		}
74
75		resp, _, err := client.Search().PrefixSearch(a.Last, contexts.Jobs, nil)
76		if err != nil {
77			return []string{}
78		}
79		return resp.Matches[contexts.Jobs]
80	})
81}
82
83func (c *JobDispatchCommand) Name() string { return "job dispatch" }
84
85func (c *JobDispatchCommand) Run(args []string) int {
86	var detach, verbose bool
87	var meta []string
88
89	flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
90	flags.Usage = func() { c.Ui.Output(c.Help()) }
91	flags.BoolVar(&detach, "detach", false, "")
92	flags.BoolVar(&verbose, "verbose", false, "")
93	flags.Var((*flaghelper.StringFlag)(&meta), "meta", "")
94
95	if err := flags.Parse(args); err != nil {
96		return 1
97	}
98
99	// Truncate the id unless full length is requested
100	length := shortId
101	if verbose {
102		length = fullId
103	}
104
105	// Check that we got one or two arguments
106	args = flags.Args()
107	if l := len(args); l < 1 || l > 2 {
108		c.Ui.Error("This command takes one or two argument: <parameterized job> [input source]")
109		c.Ui.Error(commandErrorText(c))
110		return 1
111	}
112
113	job := args[0]
114	var payload []byte
115	var readErr error
116
117	// Read the input
118	if len(args) == 2 {
119		switch args[1] {
120		case "-":
121			payload, readErr = ioutil.ReadAll(os.Stdin)
122		default:
123			payload, readErr = ioutil.ReadFile(args[1])
124		}
125		if readErr != nil {
126			c.Ui.Error(fmt.Sprintf("Error reading input data: %v", readErr))
127			return 1
128		}
129	}
130
131	// Build the meta
132	metaMap := make(map[string]string, len(meta))
133	for _, m := range meta {
134		split := strings.SplitN(m, "=", 2)
135		if len(split) != 2 {
136			c.Ui.Error(fmt.Sprintf("Error parsing meta value: %v", m))
137			return 1
138		}
139
140		metaMap[split[0]] = split[1]
141	}
142
143	// Get the HTTP client
144	client, err := c.Meta.Client()
145	if err != nil {
146		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
147		return 1
148	}
149
150	// Dispatch the job
151	resp, _, err := client.Jobs().Dispatch(job, metaMap, payload, nil)
152	if err != nil {
153		c.Ui.Error(fmt.Sprintf("Failed to dispatch job: %s", err))
154		return 1
155	}
156
157	// See if an evaluation was created. If the job is periodic there will be no
158	// eval.
159	evalCreated := resp.EvalID != ""
160
161	basic := []string{
162		fmt.Sprintf("Dispatched Job ID|%s", resp.DispatchedJobID),
163	}
164	if evalCreated {
165		basic = append(basic, fmt.Sprintf("Evaluation ID|%s", limit(resp.EvalID, length)))
166	}
167	c.Ui.Output(formatKV(basic))
168
169	// Nothing to do
170	if detach || !evalCreated {
171		return 0
172	}
173
174	c.Ui.Output("")
175	mon := newMonitor(c.Ui, client, length)
176	return mon.monitor(resp.EvalID, false)
177}
178