1package command
2
3import (
4	"fmt"
5	"strings"
6
7	"github.com/hashicorp/nomad/api/contexts"
8	"github.com/mitchellh/cli"
9	"github.com/posener/complete"
10)
11
12type StatusCommand struct {
13	Meta
14
15	// Placeholder bool to allow passing of verbose flags to subcommands.
16	verbose bool
17}
18
19func (s *StatusCommand) Help() string {
20	helpText := `
21Usage: nomad status [options] <identifier>
22
23  Display the status output for any given resource. The command will
24  detect the type of resource being queried and display the appropriate
25  status output.
26
27General Options:
28
29  ` + generalOptionsUsage() + `
30
31Status Options:
32
33  -verbose
34    Display full information.
35`
36
37	return strings.TrimSpace(helpText)
38}
39
40func (c *StatusCommand) Synopsis() string {
41	return "Display the status output for a resource"
42}
43
44func (c *StatusCommand) AutocompleteFlags() complete.Flags {
45	return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
46		complete.Flags{
47			"-verbose": complete.PredictNothing,
48		})
49}
50
51func (c *StatusCommand) AutocompleteArgs() complete.Predictor {
52	return complete.PredictFunc(func(a complete.Args) []string {
53		client, err := c.Meta.Client()
54		if err != nil {
55			return nil
56		}
57
58		resp, _, err := client.Search().PrefixSearch(a.Last, contexts.All, nil)
59		if err != nil {
60			return []string{}
61		}
62
63		final := make([]string, 0)
64
65		for _, matches := range resp.Matches {
66			if len(matches) == 0 {
67				continue
68			}
69
70			final = append(final, matches...)
71		}
72
73		return final
74	})
75}
76
77func (c *StatusCommand) Run(args []string) int {
78	flags := c.Meta.FlagSet("status", FlagSetClient)
79	flags.Usage = func() { c.Ui.Output(c.Help()) }
80	flags.BoolVar(&c.verbose, "verbose", false, "")
81
82	if err := flags.Parse(args); err != nil {
83		c.Ui.Error(fmt.Sprintf("Error parsing arguments: %q", err))
84		return 1
85	}
86
87	// Store the original arguments so we can pass them to the routed command
88	argsCopy := args
89
90	// Check that we got exactly one evaluation ID
91	args = flags.Args()
92
93	// Get the HTTP client
94	client, err := c.Meta.Client()
95	if err != nil {
96		c.Ui.Error(fmt.Sprintf("Error initializing client: %q", err))
97		return 1
98	}
99
100	// If no identifier is provided, default to listing jobs
101	if len(args) == 0 {
102		cmd := &JobStatusCommand{Meta: c.Meta}
103		return cmd.Run(argsCopy)
104	}
105
106	id := args[len(args)-1]
107
108	// Query for the context associated with the id
109	res, _, err := client.Search().PrefixSearch(id, contexts.All, nil)
110	if err != nil {
111		c.Ui.Error(fmt.Sprintf("Error querying search with id: %q", err))
112		return 1
113	}
114
115	if res.Matches == nil {
116		c.Ui.Error(fmt.Sprintf("No matches returned for query: %q", err))
117		return 1
118	}
119
120	var match contexts.Context
121	exactMatches := 0
122	for ctx, vers := range res.Matches {
123		if len(vers) > 0 && vers[0] == id {
124			match = ctx
125			exactMatches++
126		}
127	}
128
129	if exactMatches > 1 {
130		c.logMultiMatchError(id, res.Matches)
131		return 1
132	} else if exactMatches == 0 {
133		matchCount := 0
134		for ctx, vers := range res.Matches {
135			l := len(vers)
136			if l == 1 {
137				match = ctx
138				matchCount++
139			}
140
141			// Only a single result should return, as this is a match against a full id
142			if matchCount > 1 || l > 1 {
143				c.logMultiMatchError(id, res.Matches)
144				return 1
145			}
146		}
147	}
148
149	var cmd cli.Command
150	switch match {
151	case contexts.Evals:
152		cmd = &EvalStatusCommand{Meta: c.Meta}
153	case contexts.Nodes:
154		cmd = &NodeStatusCommand{Meta: c.Meta}
155	case contexts.Allocs:
156		cmd = &AllocStatusCommand{Meta: c.Meta}
157	case contexts.Jobs:
158		cmd = &JobStatusCommand{Meta: c.Meta}
159	case contexts.Deployments:
160		cmd = &DeploymentStatusCommand{Meta: c.Meta}
161	case contexts.Namespaces:
162		cmd = &NamespaceStatusCommand{Meta: c.Meta}
163	case contexts.Quotas:
164		cmd = &QuotaStatusCommand{Meta: c.Meta}
165	default:
166		c.Ui.Error(fmt.Sprintf("Unable to resolve ID: %q", id))
167		return 1
168	}
169
170	return cmd.Run(argsCopy)
171}
172
173// logMultiMatchError is used to log an error message when multiple matches are
174// found. The error message logged displays the matched IDs per context.
175func (c *StatusCommand) logMultiMatchError(id string, matches map[contexts.Context][]string) {
176	c.Ui.Error(fmt.Sprintf("Multiple matches found for id %q", id))
177	for ctx, vers := range matches {
178		if len(vers) == 0 {
179			continue
180		}
181
182		c.Ui.Error(fmt.Sprintf("\n%s:", strings.Title(string(ctx))))
183		c.Ui.Error(fmt.Sprintf("%s", strings.Join(vers, ", ")))
184	}
185}
186