diff --git a/command/volume_snapshot_list.go b/command/volume_snapshot_list.go index 848552118790..814081aaab32 100644 --- a/command/volume_snapshot_list.go +++ b/command/volume_snapshot_list.go @@ -3,6 +3,7 @@ package command import ( "fmt" "io" + "os" "sort" "strings" @@ -41,6 +42,12 @@ List Options: -secret Secrets to pass to the plugin to list snapshots. Accepts multiple flags in the form -secret key=value + + -per-page + How many results to show per page. Defaults to 30. + + -page-token + Where to start pagination. ` return strings.TrimSpace(helpText) } @@ -75,12 +82,16 @@ func (c *VolumeSnapshotListCommand) Run(args []string) int { var pluginID string var verbose bool var secretsArgs flaghelper.StringFlag + var perPage int + var pageToken string flags := c.Meta.FlagSet(c.Name(), FlagSetClient) flags.Usage = func() { c.Ui.Output(c.Help()) } flags.StringVar(&pluginID, "plugin", "", "") flags.BoolVar(&verbose, "verbose", false, "") flags.Var(&secretsArgs, "secret", "secrets for snapshot, ex. -secret key=value") + flags.IntVar(&perPage, "per-page", 30, "") + flags.StringVar(&pageToken, "page-token", "", "") if err := flags.Parse(args); err != nil { c.Ui.Error(fmt.Sprintf("Error parsing arguments %s", err)) @@ -132,32 +143,33 @@ func (c *VolumeSnapshotListCommand) Run(args []string) int { } req := &api.CSISnapshotListRequest{ - PluginID: pluginID, - Secrets: secrets, - QueryOptions: api.QueryOptions{PerPage: 30}, + PluginID: pluginID, + Secrets: secrets, + QueryOptions: api.QueryOptions{ + PerPage: int32(perPage), + NextToken: pageToken, + Params: map[string]string{}, + }, } - for { - resp, _, err := client.CSIVolumes().ListSnapshotsOpts(req) - if err != nil && !errors.Is(err, io.EOF) { - c.Ui.Error(fmt.Sprintf( - "Error querying CSI external snapshots for plugin %q: %s", pluginID, err)) - return 1 - } - if resp == nil || len(resp.Snapshots) == 0 { - // several plugins return EOF once you hit the end of the page, - // rather than an empty list - break - } + resp, _, err := client.CSIVolumes().ListSnapshotsOpts(req) + if err != nil && !errors.Is(err, io.EOF) { + c.Ui.Error(fmt.Sprintf( + "Error querying CSI external snapshots for plugin %q: %s", pluginID, err)) + return 1 + } + if resp == nil || len(resp.Snapshots) == 0 { + // several plugins return EOF once you hit the end of the page, + // rather than an empty list + return 0 + } - c.Ui.Output(csiFormatSnapshots(resp.Snapshots, verbose)) - req.NextToken = resp.NextToken - if req.NextToken == "" { - break - } - // we can't know the shape of arbitrarily-sized lists of snapshots, - // so break after each page - c.Ui.Output("...") + c.Ui.Output(csiFormatSnapshots(resp.Snapshots, verbose)) + + if resp.NextToken != "" { + c.Ui.Output(fmt.Sprintf(` +Results have been paginated. To get the next page run: +%s -page-token %s`, argsWithoutPageToken(os.Args), resp.NextToken)) } return 0 diff --git a/e2e/csi/ebs.go b/e2e/csi/ebs.go index 880e064f936a..67184ef229f4 100644 --- a/e2e/csi/ebs.go +++ b/e2e/csi/ebs.go @@ -176,7 +176,11 @@ func (tc *CSIControllerPluginEBSTest) TestSnapshot(f *framework.F) { f.NoError(err, fmt.Sprintf("could not parse output:\n%v", out)) f.Len(snaps, 1, fmt.Sprintf("could not parse output:\n%v", out)) - out, err = e2e.Command("nomad", "volume", "snapshot", "list", "-plugin", ebsPluginID) + // the snapshot we're looking for should be the first one because + // we just created it, but give us some breathing room to allow + // for concurrent test runs + out, err = e2e.Command("nomad", "volume", "snapshot", "list", + "-plugin", ebsPluginID, "-per-page", "10") requireNoErrorElseDump(f, err, "could not list volume snapshots", tc.pluginJobIDs) f.Contains(out, snaps[0]["ID"], fmt.Sprintf("volume snapshot list did not include expected snapshot:\n%v", out)) diff --git a/website/content/docs/commands/volume/snapshot-list.mdx b/website/content/docs/commands/volume/snapshot-list.mdx index 0135cf967387..747de707f9aa 100644 --- a/website/content/docs/commands/volume/snapshot-list.mdx +++ b/website/content/docs/commands/volume/snapshot-list.mdx @@ -36,6 +36,8 @@ Nomad. matching plugins will be displayed. - `-secret`: Secrets to pass to the plugin to list snapshots. Accepts multiple flags in the form `-secret key=value` +- `-per-page`: How many results to show per page. +- `-page-token`: Where to start pagination. When ACLs are enabled, this command requires a token with the `csi-list-volumes` capability for the plugin's namespace.