// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package command import ( "fmt" "strings" "github.com/hashicorp/cli" "github.com/hashicorp/nomad/api/contexts" "github.com/posener/complete" ) type StatusCommand struct { Meta // Placeholder bool to allow passing of verbose flags to subcommands. verbose bool openURL bool } func (c *StatusCommand) Help() string { helpText := ` Usage: nomad status [options] Display the status output for any given resource. The command will detect the type of resource being queried and display the appropriate status output. If no arguments are provided, the command will fallback to "nomad job status", which will list all jobs. General Options: ` + generalOptionsUsage(usageOptsDefault) + ` Status Options: -verbose Display full information. -ui Open the status page in the browser. ` return strings.TrimSpace(helpText) } func (c *StatusCommand) Synopsis() string { return "Display the status output for a resource" } func (c *StatusCommand) AutocompleteFlags() complete.Flags { return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient), complete.Flags{ "-verbose": complete.PredictNothing, "-ui": complete.PredictNothing, }) } func (c *StatusCommand) AutocompleteArgs() complete.Predictor { return complete.PredictFunc(func(a complete.Args) []string { client, err := c.Meta.Client() if err != nil { return nil } resp, _, err := client.Search().PrefixSearch(a.Last, contexts.All, nil) if err != nil { return []string{} } final := make([]string, 0) for _, matches := range resp.Matches { if len(matches) == 0 { continue } final = append(final, matches...) } return final }) } func (c *StatusCommand) Run(args []string) int { flags := c.Meta.FlagSet("status", FlagSetClient) flags.Usage = func() { c.Ui.Output(c.Help()) } flags.BoolVar(&c.verbose, "verbose", false, "") flags.BoolVar(&c.openURL, "ui", false, "") if err := flags.Parse(args); err != nil { c.Ui.Error(fmt.Sprintf("Error parsing arguments: %q", err)) return 1 } // Store the original arguments so we can pass them to the routed command argsCopy := args // Check that we got exactly one evaluation ID args = flags.Args() // Get the HTTP client client, err := c.Meta.Client() if err != nil { c.Ui.Error(fmt.Sprintf("Error initializing client: %q", err)) return 1 } // If no identifier is provided, default to listing jobs if len(args) == 0 { cmd := &JobStatusCommand{Meta: c.Meta} return cmd.Run(argsCopy) } id := args[len(args)-1] // Query for the context associated with the id res, _, err := client.Search().PrefixSearch(id, contexts.All, nil) if err != nil { c.Ui.Error(fmt.Sprintf("Error querying search with id: %q", err)) return 1 } if res.Matches == nil { c.Ui.Error(fmt.Sprintf("No matches returned for query: %q", err)) return 1 } var match contexts.Context exactMatches := 0 for ctx, vers := range res.Matches { if len(vers) > 0 && vers[0] == id { match = ctx exactMatches++ } } if exactMatches > 1 { c.logMultiMatchError(id, res.Matches) return 1 } else if exactMatches == 0 { matchCount := 0 for ctx, vers := range res.Matches { l := len(vers) if l == 1 { match = ctx matchCount++ } // Only a single result should return, as this is a match against a full id if matchCount > 1 || l > 1 { c.logMultiMatchError(id, res.Matches) return 1 } } } var cmd cli.Command switch match { case contexts.Evals: cmd = &EvalStatusCommand{Meta: c.Meta} case contexts.Nodes: cmd = &NodeStatusCommand{Meta: c.Meta} case contexts.Allocs: cmd = &AllocStatusCommand{Meta: c.Meta} case contexts.Jobs: cmd = &JobStatusCommand{Meta: c.Meta} case contexts.Deployments: cmd = &DeploymentStatusCommand{Meta: c.Meta} case contexts.Namespaces: cmd = &NamespaceStatusCommand{Meta: c.Meta} case contexts.Quotas: cmd = &QuotaStatusCommand{Meta: c.Meta} case contexts.Plugins: cmd = &PluginStatusCommand{Meta: c.Meta} case contexts.Volumes: cmd = &VolumeStatusCommand{Meta: c.Meta} default: c.Ui.Error(fmt.Sprintf("Unable to resolve ID: %q", id)) return 1 } return cmd.Run(argsCopy) } // logMultiMatchError is used to log an error message when multiple matches are // found. The error message logged displays the matched IDs per context. func (c *StatusCommand) logMultiMatchError(id string, matches map[contexts.Context][]string) { c.Ui.Error(fmt.Sprintf("Multiple matches found for id %q", id)) for ctx, vers := range matches { if len(vers) == 0 { continue } c.Ui.Error(fmt.Sprintf("\n%s:", strings.Title(string(ctx)))) c.Ui.Error(strings.Join(vers, ", ")) } }