1package command
2
3import (
4	"fmt"
5	"os"
6	"strings"
7
8	"github.com/hashicorp/nomad/api/contexts"
9	"github.com/posener/complete"
10)
11
12type JobRevertCommand struct {
13	Meta
14}
15
16func (c *JobRevertCommand) Help() string {
17	helpText := `
18Usage: nomad job revert [options] <job> <version>
19
20  Revert is used to revert a job to a prior version of the job. The available
21  versions to revert to can be found using "nomad job history" command.
22
23General Options:
24
25  ` + generalOptionsUsage() + `
26
27Revert Options:
28
29  -detach
30    Return immediately instead of entering monitor mode. After job revert,
31    the evaluation ID will be printed to the screen, which can be used to
32    examine the evaluation using the eval-status command.
33
34  -vault-token
35   The Vault token used to verify that the caller has access to the Vault
36   policies i the targeted version of the job.
37
38  -verbose
39    Display full information.
40`
41	return strings.TrimSpace(helpText)
42}
43
44func (c *JobRevertCommand) Synopsis() string {
45	return "Revert to a prior version of the job"
46}
47
48func (c *JobRevertCommand) AutocompleteFlags() complete.Flags {
49	return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
50		complete.Flags{
51			"-detach":  complete.PredictNothing,
52			"-verbose": complete.PredictNothing,
53		})
54}
55
56func (c *JobRevertCommand) AutocompleteArgs() complete.Predictor {
57	return complete.PredictFunc(func(a complete.Args) []string {
58		client, err := c.Meta.Client()
59		if err != nil {
60			return nil
61		}
62
63		resp, _, err := client.Search().PrefixSearch(a.Last, contexts.Jobs, nil)
64		if err != nil {
65			return []string{}
66		}
67		return resp.Matches[contexts.Jobs]
68	})
69}
70
71func (c *JobRevertCommand) Name() string { return "job revert" }
72
73func (c *JobRevertCommand) Run(args []string) int {
74	var detach, verbose bool
75	var vaultToken string
76
77	flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
78	flags.Usage = func() { c.Ui.Output(c.Help()) }
79	flags.BoolVar(&detach, "detach", false, "")
80	flags.BoolVar(&verbose, "verbose", false, "")
81	flags.StringVar(&vaultToken, "vault-token", "", "")
82
83	if err := flags.Parse(args); err != nil {
84		return 1
85	}
86
87	// Truncate the id unless full length is requested
88	length := shortId
89	if verbose {
90		length = fullId
91	}
92
93	// Check that we got two args
94	args = flags.Args()
95	if l := len(args); l != 2 {
96		c.Ui.Error("This command takes two arguments: <job> <version>")
97		c.Ui.Error(commandErrorText(c))
98		return 1
99	}
100
101	// Get the HTTP client
102	client, err := c.Meta.Client()
103	if err != nil {
104		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
105		return 1
106	}
107
108	// Parse the Vault token
109	if vaultToken == "" {
110		// Check the environment variable
111		vaultToken = os.Getenv("VAULT_TOKEN")
112	}
113
114	jobID := args[0]
115	revertVersion, ok, err := parseVersion(args[1])
116	if !ok {
117		c.Ui.Error("The job version to revert to must be specified using the -job-version flag")
118		return 1
119	}
120	if err != nil {
121		c.Ui.Error(fmt.Sprintf("Failed to parse job-version flag: %v", err))
122		return 1
123	}
124
125	// Check if the job exists
126	jobs, _, err := client.Jobs().PrefixList(jobID)
127	if err != nil {
128		c.Ui.Error(fmt.Sprintf("Error listing jobs: %s", err))
129		return 1
130	}
131	if len(jobs) == 0 {
132		c.Ui.Error(fmt.Sprintf("No job(s) with prefix or id %q found", jobID))
133		return 1
134	}
135	if len(jobs) > 1 && strings.TrimSpace(jobID) != jobs[0].ID {
136		c.Ui.Error(fmt.Sprintf("Prefix matched multiple jobs\n\n%s", createStatusListOutput(jobs)))
137		return 1
138	}
139
140	// Prefix lookup matched a single job
141	resp, _, err := client.Jobs().Revert(jobs[0].ID, revertVersion, nil, nil, vaultToken)
142	if err != nil {
143		c.Ui.Error(fmt.Sprintf("Error retrieving job versions: %s", err))
144		return 1
145	}
146
147	// Nothing to do
148	evalCreated := resp.EvalID != ""
149	if detach || !evalCreated {
150		return 0
151	}
152
153	mon := newMonitor(c.Ui, client, length)
154	return mon.monitor(resp.EvalID, false)
155}
156