From 2c6966c61aa6fb37f3fbbad382da309d3fb55aa9 Mon Sep 17 00:00:00 2001 From: James Rasell Date: Fri, 22 Apr 2022 10:32:40 +0200 Subject: [PATCH] cli: add pagination flags to service info command. (#12730) --- .../agent/service_registration_endpoint.go | 2 +- command/service_info.go | 74 +++++++++++++++++-- command/service_info_test.go | 55 ++++++++++++++ .../content/docs/commands/service/info.mdx | 6 ++ 4 files changed, 128 insertions(+), 9 deletions(-) diff --git a/command/agent/service_registration_endpoint.go b/command/agent/service_registration_endpoint.go index bf89e62c8..9107f8006 100644 --- a/command/agent/service_registration_endpoint.go +++ b/command/agent/service_registration_endpoint.go @@ -99,7 +99,7 @@ func (s *HTTPServer) serviceGetRequest( if err := s.agent.RPC(structs.ServiceRegistrationGetServiceRPCMethod, &args, &reply); err != nil { return nil, err } - setIndex(resp, reply.Index) + setMeta(resp, &reply.QueryMeta) if reply.Services == nil { reply.Services = make([]*structs.ServiceRegistration, 0) diff --git a/command/service_info.go b/command/service_info.go index 0297712ad..f122c7fd3 100644 --- a/command/service_info.go +++ b/command/service_info.go @@ -2,6 +2,7 @@ package command import ( "fmt" + "os" "sort" "strings" @@ -37,6 +38,12 @@ Service Info Options: -verbose Display full information. + -per-page + How many results to show per page. + + -page-token + Where to start pagination. + -filter Specifies an expression used to filter query results. @@ -57,10 +64,12 @@ func (s *ServiceInfoCommand) Synopsis() string { func (s *ServiceInfoCommand) AutocompleteFlags() complete.Flags { return mergeAutocompleteFlags(s.Meta.AutocompleteFlags(FlagSetClient), complete.Flags{ - "-json": complete.PredictNothing, - "-filter": complete.PredictAnything, - "-t": complete.PredictAnything, - "-verbose": complete.PredictNothing, + "-json": complete.PredictNothing, + "-filter": complete.PredictAnything, + "-per-page": complete.PredictAnything, + "-page-token": complete.PredictAnything, + "-t": complete.PredictAnything, + "-verbose": complete.PredictNothing, }) } @@ -70,8 +79,9 @@ func (s *ServiceInfoCommand) Name() string { return "service info" } // Run satisfies the cli.Command Run function. func (s *ServiceInfoCommand) Run(args []string) int { var ( - json, verbose bool - tmpl, filter string + json, verbose bool + perPage int + tmpl, filter, pageToken string ) flags := s.Meta.FlagSet(s.Name(), FlagSetClient) @@ -80,6 +90,8 @@ func (s *ServiceInfoCommand) Run(args []string) int { flags.BoolVar(&verbose, "verbose", false, "") flags.StringVar(&tmpl, "t", "", "") flags.StringVar(&filter, "filter", "", "") + flags.IntVar(&perPage, "per-page", 0, "") + flags.StringVar(&pageToken, "page-token", "", "") if err := flags.Parse(args); err != nil { return 1 } @@ -99,10 +111,12 @@ func (s *ServiceInfoCommand) Run(args []string) int { // Set up the options to capture any filter passed. opts := api.QueryOptions{ - Filter: filter, + Filter: filter, + PerPage: int32(perPage), + NextToken: pageToken, } - serviceInfo, _, err := client.Services().Get(args[0], &opts) + serviceInfo, qm, err := client.Services().Get(args[0], &opts) if err != nil { s.Ui.Error(fmt.Sprintf("Error listing service registrations: %s", err)) return 1 @@ -147,6 +161,12 @@ func (s *ServiceInfoCommand) Run(args []string) int { } else { s.formatOutput(sortedJobID, jobIDServices) } + + if qm.NextToken != "" { + s.Ui.Output(fmt.Sprintf("\nResults have been paginated. To get the next page run: \n\n%s ", + argsWithNewPageToken(os.Args, qm.NextToken))) + } + return 0 } @@ -194,3 +214,41 @@ func (s *ServiceInfoCommand) formatVerboseOutput(jobIDs []string, jobServices ma } } } + +// argsWithNewPageToken takes the arguments which called the CLI and modifies +// them to include the correct next token. The function ensures the argument +// ordering is maintained which is vital when using pagination on info related +// calls which have an identifier as their final argument. +func argsWithNewPageToken(osArgs []string, nextToken string) string { + + // Copy the arguments into a new array which will be modified and make a + // note of the original length as we may need to modify the length if this + // is the first pagination call without a next token. + newArgs := osArgs + numArgs := len(newArgs) + + for i := 0; i < numArgs; i++ { + + // If the caller already included a pagination token, replace this + // occurrence with the new next token and exit as we don't need to + // modify any other arguments. + if strings.HasPrefix(newArgs[i], "-page-token") { + if strings.Contains(newArgs[i], "=") { + newArgs[i] = "-page-token=" + nextToken + } else { + newArgs[i+1] = nextToken + } + break + } + + // If we have reached the final argument (service name) and are still + // looping we have not added the next token argument. Add this while + // ensuring the service name if the final argument on the command. + if i == numArgs-1 { + serviceName := newArgs[i] + newArgs[i] = "-page-token=" + nextToken + newArgs = append(newArgs, serviceName) + } + } + return strings.Join(newArgs, " ") +} diff --git a/command/service_info_test.go b/command/service_info_test.go index 08990deed..c2c86bdb5 100644 --- a/command/service_info_test.go +++ b/command/service_info_test.go @@ -120,3 +120,58 @@ func TestServiceInfoCommand_Run(t *testing.T) { ui.OutputWriter.Reset() ui.ErrorWriter.Reset() } + +func Test_argsWithNewPageToken(t *testing.T) { + ci.Parallel(t) + + testCases := []struct { + inputOsArgs []string + inputNextToken string + expectedOutput string + name string + }{ + { + inputOsArgs: []string{"nomad", "service", "info", "-page-token=abcdef", "example-cache"}, + inputNextToken: "ghijkl", + expectedOutput: "nomad service info -page-token=ghijkl example-cache", + name: "page token with equals sign", + }, + { + inputOsArgs: []string{"nomad", "service", "info", "-page-token", "abcdef", "example-cache"}, + inputNextToken: "ghijkl", + expectedOutput: "nomad service info -page-token ghijkl example-cache", + name: "page token with whitespace gap", + }, + { + inputOsArgs: []string{"nomad", "service", "info", "-per-page", "3", "-page-token", "abcdef", "example-cache"}, + inputNextToken: "ghijkl", + expectedOutput: "nomad service info -per-page 3 -page-token ghijkl example-cache", + name: "per page and page token", + }, + { + inputOsArgs: []string{"nomad", "service", "info", "-page-token", "abcdef", "-per-page", "3", "example-cache"}, + inputNextToken: "ghijkl", + expectedOutput: "nomad service info -page-token ghijkl -per-page 3 example-cache", + name: "page token and per page", + }, + { + inputOsArgs: []string{"nomad", "service", "info", "-page-token", "abcdef", "-per-page=3", "example-cache"}, + inputNextToken: "ghijkl", + expectedOutput: "nomad service info -page-token ghijkl -per-page=3 example-cache", + name: "page token and per page with equal", + }, + { + inputOsArgs: []string{"nomad", "service", "info", "-verbose", "-page-token", "abcdef", "-per-page=3", "example-cache"}, + inputNextToken: "ghijkl", + expectedOutput: "nomad service info -verbose -page-token ghijkl -per-page=3 example-cache", + name: "page token per page with verbose", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + actualOutput := argsWithNewPageToken(tc.inputOsArgs, tc.inputNextToken) + require.Equal(t, tc.expectedOutput, actualOutput) + }) + } +} diff --git a/website/content/docs/commands/service/info.mdx b/website/content/docs/commands/service/info.mdx index 6ff9e2bb9..9287f4da0 100644 --- a/website/content/docs/commands/service/info.mdx +++ b/website/content/docs/commands/service/info.mdx @@ -30,6 +30,12 @@ capability for the service's namespace. ## Info Options +- `-per-page`: How many results to show per page. + +- `-page-token`: Where to start pagination. + +- `-filter`: Specifies an expression used to filter query results. + - `-json` : Output the service registrations in JSON format. - `-t` : Format and display the service registrations using a Go template.