mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
The `-type` option for `volume status` is a UX papercut because for many clusters there will be only one sort of volume in use. Update the CLI so that the default behavior is to query CSI and/or DHV. This behavior is subtly different when the user provides an ID or not. If the user doesn't provide an ID, we query both CSI and DHV and show both tables. If the user provides an ID, we query DHV first and then CSI, and show only the appropriate volume. Because DHV IDs are UUIDs, we're sure we won't have collisions between the two. We only show errors if both queries return an error. Fixes: https://hashicorp.atlassian.net/browse/NET-12214
214 lines
5.2 KiB
Go
214 lines
5.2 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package command
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/nomad/api/contexts"
|
|
"github.com/posener/complete"
|
|
)
|
|
|
|
type VolumeStatusCommand struct {
|
|
Meta
|
|
length int
|
|
short bool
|
|
verbose bool
|
|
json bool
|
|
template string
|
|
}
|
|
|
|
func (c *VolumeStatusCommand) Help() string {
|
|
helpText := `
|
|
Usage: nomad volume status [options] <id>
|
|
|
|
Display status information about a CSI volume. If no volume id is given, a
|
|
list of all volumes will be displayed.
|
|
|
|
When ACLs are enabled, this command requires a token with the
|
|
'csi-read-volume' and 'csi-list-volumes' capability for the volume's
|
|
namespace.
|
|
|
|
General Options:
|
|
|
|
` + generalOptionsUsage(usageOptsDefault) + `
|
|
|
|
Status Options:
|
|
|
|
-type <type>
|
|
List only volumes of type <type> (one of "host" or "csi"). If omitted, the
|
|
command will query for both dynamic host volumes and CSI volumes.
|
|
|
|
-short
|
|
Display short output. Used only when a single volume is being
|
|
queried, and drops verbose information about allocations.
|
|
|
|
-verbose
|
|
Display full volumes information.
|
|
|
|
-json
|
|
Output the volumes in JSON format.
|
|
|
|
-t
|
|
Format and display volumes using a Go template.
|
|
|
|
-node-pool <pool>
|
|
Filter results by node pool, when no volume ID is provided and -type=host.
|
|
|
|
-node <node ID>
|
|
Filter results by node ID, when no volume ID is provided and -type=host.
|
|
`
|
|
return strings.TrimSpace(helpText)
|
|
}
|
|
|
|
func (c *VolumeStatusCommand) Synopsis() string {
|
|
return "Display status information about a volume"
|
|
}
|
|
|
|
func (c *VolumeStatusCommand) AutocompleteFlags() complete.Flags {
|
|
return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
|
|
complete.Flags{
|
|
"-type": complete.PredictSet("csi", "host"),
|
|
"-short": complete.PredictNothing,
|
|
"-verbose": complete.PredictNothing,
|
|
"-json": complete.PredictNothing,
|
|
"-t": complete.PredictAnything,
|
|
"-node": nodePredictor(c.Client, nil),
|
|
"-node-pool": nodePoolPredictor(c.Client, nil),
|
|
})
|
|
}
|
|
|
|
func (c *VolumeStatusCommand) 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.Volumes, nil)
|
|
if err != nil {
|
|
return []string{}
|
|
}
|
|
matches := resp.Matches[contexts.Volumes]
|
|
|
|
resp, _, err = client.Search().PrefixSearch(a.Last, contexts.HostVolumes, nil)
|
|
if err != nil {
|
|
return []string{}
|
|
}
|
|
matches = append(matches, resp.Matches[contexts.HostVolumes]...)
|
|
return matches
|
|
})
|
|
}
|
|
|
|
func (c *VolumeStatusCommand) Name() string { return "volume status" }
|
|
|
|
func (c *VolumeStatusCommand) Run(args []string) int {
|
|
var typeArg, nodeID, nodePool string
|
|
|
|
flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
|
|
flags.Usage = func() { c.Ui.Output(c.Help()) }
|
|
flags.StringVar(&typeArg, "type", "", "")
|
|
flags.BoolVar(&c.short, "short", false, "")
|
|
flags.BoolVar(&c.verbose, "verbose", false, "")
|
|
flags.BoolVar(&c.json, "json", false, "")
|
|
flags.StringVar(&c.template, "t", "", "")
|
|
flags.StringVar(&nodeID, "node", "", "")
|
|
flags.StringVar(&nodePool, "node-pool", "", "")
|
|
|
|
if err := flags.Parse(args); err != nil {
|
|
c.Ui.Error(fmt.Sprintf("Error parsing arguments %s", err))
|
|
return 1
|
|
}
|
|
|
|
// Check that we either got no arguments or exactly one
|
|
args = flags.Args()
|
|
if len(args) > 1 {
|
|
c.Ui.Error("This command takes either no arguments or one: <id>")
|
|
c.Ui.Error(commandErrorText(c))
|
|
return 1
|
|
}
|
|
|
|
// Truncate alloc and node IDs unless full length is requested
|
|
c.length = shortId
|
|
if c.verbose {
|
|
c.length = fullId
|
|
}
|
|
|
|
// Get the HTTP client
|
|
client, err := c.Meta.Client()
|
|
if err != nil {
|
|
c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
|
|
return 1
|
|
}
|
|
|
|
id := ""
|
|
if len(args) == 1 {
|
|
id = args[0]
|
|
}
|
|
|
|
opts := formatOpts{
|
|
verbose: c.verbose,
|
|
short: c.short,
|
|
length: c.length,
|
|
json: c.json,
|
|
template: c.template,
|
|
}
|
|
|
|
switch typeArg {
|
|
case "csi":
|
|
if nodeID != "" || nodePool != "" {
|
|
c.Ui.Error("-node and -node-pool can only be used with -type host")
|
|
return 1
|
|
}
|
|
if err := c.csiVolumeStatus(client, id, opts); err != nil {
|
|
c.Ui.Error(err.Error())
|
|
return 1
|
|
}
|
|
case "host":
|
|
if err := c.hostVolumeStatus(client, id, nodeID, nodePool, opts); err != nil {
|
|
c.Ui.Error(err.Error())
|
|
return 1
|
|
}
|
|
case "":
|
|
if id == "" {
|
|
// for list, we want to show both
|
|
dhvErr := c.hostVolumeList(client, nodeID, nodePool, opts)
|
|
if dhvErr != nil {
|
|
c.Ui.Error(dhvErr.Error())
|
|
}
|
|
c.Ui.Output("")
|
|
csiErr := c.csiVolumesList(client, opts)
|
|
if csiErr != nil {
|
|
c.Ui.Error(csiErr.Error())
|
|
}
|
|
if dhvErr == nil && csiErr == nil {
|
|
return 0
|
|
}
|
|
return 1
|
|
} else {
|
|
// for read, we only want to show whichever has results
|
|
hostErr := c.hostVolumeStatus(client, id, nodeID, nodePool, opts)
|
|
if hostErr != nil {
|
|
if !errors.Is(hostErr, hostVolumeListError) {
|
|
c.Ui.Error(hostErr.Error())
|
|
return 1 // we found a host volume but had some other error
|
|
}
|
|
csiErr := c.csiVolumeStatus(client, id, opts)
|
|
if csiErr != nil {
|
|
c.Ui.Error(hostErr.Error())
|
|
c.Ui.Error(csiErr.Error())
|
|
return 1
|
|
}
|
|
}
|
|
}
|
|
default:
|
|
c.Ui.Error(fmt.Sprintf("No such volume type %q", typeArg))
|
|
return 1
|
|
}
|
|
|
|
return 0
|
|
}
|