1package command
2
3import (
4	"fmt"
5	"strings"
6
7	"github.com/hashicorp/nomad/api"
8	"github.com/hashicorp/nomad/api/contexts"
9	flaghelper "github.com/hashicorp/nomad/helper/flag-helpers"
10	"github.com/posener/complete"
11)
12
13type JobPromoteCommand struct {
14	Meta
15}
16
17func (c *JobPromoteCommand) Help() string {
18	helpText := `
19Usage: nomad job promote [options] <job id>
20
21  Promote is used to promote task groups in the most recent deployment for the
22  given job. Promotion should occur when the deployment has placed canaries for a
23  task group and those canaries have been deemed healthy. When a task group is
24  promoted, the rolling upgrade of the remaining allocations is unblocked. If the
25  canaries are found to be unhealthy, the deployment may either be failed using
26  the "nomad deployment fail" command, the job can be failed forward by submitting
27  a new version or failed backwards by reverting to an older version using the
28  "nomad job revert" command.
29
30General Options:
31
32  ` + generalOptionsUsage() + `
33
34Promote Options:
35
36  -group
37    Group may be specified many times and is used to promote that particular
38    group. If no specific groups are specified, all groups are promoted.
39
40  -detach
41    Return immediately instead of entering monitor mode. After deployment
42    resume, the evaluation ID will be printed to the screen, which can be used
43    to examine the evaluation using the eval-status command.
44
45  -verbose
46    Display full information.
47`
48	return strings.TrimSpace(helpText)
49}
50
51func (c *JobPromoteCommand) Synopsis() string {
52	return "Promote a job's canaries"
53}
54
55func (c *JobPromoteCommand) AutocompleteFlags() complete.Flags {
56	return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
57		complete.Flags{
58			"-group":   complete.PredictAnything,
59			"-detach":  complete.PredictNothing,
60			"-verbose": complete.PredictNothing,
61		})
62}
63
64func (c *JobPromoteCommand) AutocompleteArgs() complete.Predictor {
65	return complete.PredictFunc(func(a complete.Args) []string {
66		client, err := c.Meta.Client()
67		if err != nil {
68			return nil
69		}
70
71		resp, _, err := client.Search().PrefixSearch(a.Last, contexts.Jobs, nil)
72		if err != nil {
73			return []string{}
74		}
75		return resp.Matches[contexts.Jobs]
76	})
77}
78
79func (c *JobPromoteCommand) Name() string { return "job promote" }
80
81func (c *JobPromoteCommand) Run(args []string) int {
82	var detach, verbose bool
83	var groups []string
84
85	flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
86	flags.Usage = func() { c.Ui.Output(c.Help()) }
87	flags.BoolVar(&detach, "detach", false, "")
88	flags.BoolVar(&verbose, "verbose", false, "")
89	flags.Var((*flaghelper.StringFlag)(&groups), "group", "")
90
91	if err := flags.Parse(args); err != nil {
92		return 1
93	}
94
95	// Check that we got exactly one argument
96	args = flags.Args()
97	if l := len(args); l != 1 {
98		c.Ui.Error("This command takes one argument: <job id>")
99		c.Ui.Error(commandErrorText(c))
100		return 1
101	}
102
103	// Truncate the id unless full length is requested
104	length := shortId
105	if verbose {
106		length = fullId
107	}
108
109	// Get the HTTP client
110	client, err := c.Meta.Client()
111	if err != nil {
112		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
113		return 1
114	}
115
116	// Check if the job exists
117	jobID := args[0]
118	jobs, _, err := client.Jobs().PrefixList(jobID)
119	if err != nil {
120		c.Ui.Error(fmt.Sprintf("Error promoting job: %s", err))
121		return 1
122	}
123	if len(jobs) == 0 {
124		c.Ui.Error(fmt.Sprintf("No job(s) with prefix or id %q found", jobID))
125		return 1
126	}
127	if len(jobs) > 1 && strings.TrimSpace(jobID) != jobs[0].ID {
128		c.Ui.Error(fmt.Sprintf("Prefix matched multiple jobs\n\n%s", createStatusListOutput(jobs)))
129		return 1
130	}
131	jobID = jobs[0].ID
132
133	// Do a prefix lookup
134	deploy, _, err := client.Jobs().LatestDeployment(jobID, nil)
135	if err != nil {
136		c.Ui.Error(fmt.Sprintf("Error retrieving deployment: %s", err))
137		return 1
138	}
139
140	if deploy == nil {
141		c.Ui.Error(fmt.Sprintf("Job %q has no deployment to promote", jobID))
142		return 1
143	}
144
145	var u *api.DeploymentUpdateResponse
146	if len(groups) == 0 {
147		u, _, err = client.Deployments().PromoteAll(deploy.ID, nil)
148	} else {
149		u, _, err = client.Deployments().PromoteGroups(deploy.ID, groups, nil)
150	}
151
152	if err != nil {
153		c.Ui.Error(fmt.Sprintf("Error promoting deployment %q for job %q: %s", deploy.ID, jobID, err))
154		return 1
155	}
156
157	// Nothing to do
158	evalCreated := u.EvalID != ""
159	if detach || !evalCreated {
160		return 0
161	}
162
163	mon := newMonitor(c.Ui, client, length)
164	return mon.monitor(u.EvalID, false)
165}
166