mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Add --limit
option to docker search
This fix tries to address the issue raised in #23055. Currently `docker search` result caps at 25 and there is no way to allow getting more results (if exist). This fix adds the flag `--limit` so that it is possible to return more results from the `docker search`. Related documentation has been updated. Additional tests have been added to cover the changes. This fix fixes #23055. Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
This commit is contained in:
parent
98c245c9e6
commit
92f10fe228
13 changed files with 79 additions and 14 deletions
|
@ -34,6 +34,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
|
|||
cmd := Cli.Subcmd("search", []string{"TERM"}, Cli.DockerCommands["search"].Description, true)
|
||||
noTrunc := cmd.Bool([]string{"-no-trunc"}, false, "Don't truncate output")
|
||||
cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
|
||||
flLimit := cmd.Int([]string{"-limit"}, registry.DefaultSearchLimit, "Max number of search results")
|
||||
|
||||
// Deprecated since Docker 1.12 in favor of "--filter"
|
||||
automated := cmd.Bool([]string{"#-automated"}, false, "Only show automated builds - DEPRECATED")
|
||||
|
@ -72,6 +73,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
|
|||
RegistryAuth: encodedAuth,
|
||||
PrivilegeFunc: requestPrivilege,
|
||||
Filters: filterArgs,
|
||||
Limit: *flLimit,
|
||||
}
|
||||
|
||||
unorderedResults, err := cli.client.ImageSearch(ctx, name, options)
|
||||
|
|
|
@ -39,5 +39,5 @@ type importExportBackend interface {
|
|||
type registryBackend interface {
|
||||
PullImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
|
||||
PushImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
|
||||
SearchRegistryForImages(ctx context.Context, filtersArgs string, term string, authConfig *types.AuthConfig, metaHeaders map[string][]string) (*registry.SearchResults, error)
|
||||
SearchRegistryForImages(ctx context.Context, filtersArgs string, term string, limit int, authConfig *types.AuthConfig, metaHeaders map[string][]string) (*registry.SearchResults, error)
|
||||
}
|
||||
|
|
|
@ -6,12 +6,14 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/container"
|
||||
"github.com/docker/engine-api/types/versions"
|
||||
|
@ -301,7 +303,15 @@ func (s *imageRouter) getImagesSearch(ctx context.Context, w http.ResponseWriter
|
|||
headers[k] = v
|
||||
}
|
||||
}
|
||||
query, err := s.backend.SearchRegistryForImages(ctx, r.Form.Get("filters"), r.Form.Get("term"), config, headers)
|
||||
limit := registry.DefaultSearchLimit
|
||||
if r.Form.Get("limit") != "" {
|
||||
limitValue, err := strconv.Atoi(r.Form.Get("limit"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
limit = limitValue
|
||||
}
|
||||
query, err := s.backend.SearchRegistryForImages(ctx, r.Form.Get("filters"), r.Form.Get("term"), limit, config, headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ var acceptedSearchFilterTags = map[string]bool{
|
|||
|
||||
// SearchRegistryForImages queries the registry for images matching
|
||||
// term. authConfig is used to login.
|
||||
func (daemon *Daemon) SearchRegistryForImages(ctx context.Context, filtersArgs string, term string,
|
||||
func (daemon *Daemon) SearchRegistryForImages(ctx context.Context, filtersArgs string, term string, limit int,
|
||||
authConfig *types.AuthConfig,
|
||||
headers map[string][]string) (*registrytypes.SearchResults, error) {
|
||||
|
||||
|
@ -61,7 +61,7 @@ func (daemon *Daemon) SearchRegistryForImages(ctx context.Context, filtersArgs s
|
|||
}
|
||||
}
|
||||
|
||||
unfilteredResult, err := daemon.RegistryService.Search(ctx, term, authConfig, dockerversion.DockerUserAgent(ctx), headers)
|
||||
unfilteredResult, err := daemon.RegistryService.Search(ctx, term, limit, authConfig, dockerversion.DockerUserAgent(ctx), headers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ type FakeService struct {
|
|||
results []registrytypes.SearchResult
|
||||
}
|
||||
|
||||
func (s *FakeService) Search(ctx context.Context, term string, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) {
|
||||
func (s *FakeService) Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) {
|
||||
if s.shouldReturnError {
|
||||
return nil, fmt.Errorf("Search unknown error")
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ func TestSearchRegistryForImagesErrors(t *testing.T) {
|
|||
shouldReturnError: e.shouldReturnError,
|
||||
},
|
||||
}
|
||||
_, err := daemon.SearchRegistryForImages(context.Background(), e.filtersArgs, "term", nil, map[string][]string{})
|
||||
_, err := daemon.SearchRegistryForImages(context.Background(), e.filtersArgs, "term", 25, nil, map[string][]string{})
|
||||
if err == nil {
|
||||
t.Errorf("%d: expected an error, got nothing", index)
|
||||
}
|
||||
|
@ -328,7 +328,7 @@ func TestSearchRegistryForImages(t *testing.T) {
|
|||
results: s.registryResults,
|
||||
},
|
||||
}
|
||||
results, err := daemon.SearchRegistryForImages(context.Background(), s.filtersArgs, term, nil, map[string][]string{})
|
||||
results, err := daemon.SearchRegistryForImages(context.Background(), s.filtersArgs, term, 25, nil, map[string][]string{})
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", index, err)
|
||||
}
|
||||
|
|
|
@ -124,6 +124,7 @@ This section lists each version from latest to oldest. Each listing includes a
|
|||
* `GET /images/json` now supports filters `since` and `before`.
|
||||
* `POST /containers/(id or name)/start` no longer accepts a `HostConfig`.
|
||||
* `POST /images/(name)/tag` no longer has a `force` query parameter.
|
||||
* `GET /images/search` now supports maximum returned search results `limit`.
|
||||
|
||||
### v1.23 API changes
|
||||
|
||||
|
|
|
@ -2130,6 +2130,7 @@ Search for an image on [Docker Hub](https://hub.docker.com).
|
|||
Query Parameters:
|
||||
|
||||
- **term** – term to search
|
||||
- **limit** – maximum returned search results
|
||||
- **filters** – a JSON encoded value of the filters (a map[string][]string) to process on the images list. Available filters:
|
||||
- `stars=<number>`
|
||||
- `is-automated=(true|false)`
|
||||
|
|
|
@ -19,6 +19,7 @@ parent = "smn_cli"
|
|||
- is-official=(true|false)
|
||||
- stars=<number> - image has at least 'number' stars
|
||||
--help Print usage
|
||||
--limit=25 Maximum returned search results
|
||||
--no-trunc Don't truncate output
|
||||
|
||||
Search [Docker Hub](https://hub.docker.com) for images
|
||||
|
@ -74,6 +75,12 @@ at least 3 stars and the description isn't truncated in the output:
|
|||
progrium/busybox 50 [OK]
|
||||
radial/busyboxplus Full-chain, Internet enabled, busybox made from scratch. Comes in git and cURL flavors. 8 [OK]
|
||||
|
||||
## Limit search results (--limit)
|
||||
|
||||
The flag `--limit` is the maximium number of results returned by a search. This value could
|
||||
be in the range between 1 and 100. The default value of `--limit` is 25.
|
||||
|
||||
|
||||
## Filtering
|
||||
|
||||
The filtering flag (`-f` or `--filter`) format is a `key=value` pair. If there is more
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/pkg/integration/checker"
|
||||
|
@ -97,3 +98,34 @@ func (s *DockerSuite) TestSearchOnCentralRegistryWithDash(c *check.C) {
|
|||
|
||||
dockerCmd(c, "search", "ubuntu-")
|
||||
}
|
||||
|
||||
// test case for #23055
|
||||
func (s *DockerSuite) TestSearchWithLimit(c *check.C) {
|
||||
testRequires(c, Network, DaemonIsLinux)
|
||||
|
||||
limit := 10
|
||||
out, _, err := dockerCmdWithError("search", fmt.Sprintf("--limit=%d", limit), "docker")
|
||||
c.Assert(err, checker.IsNil)
|
||||
outSlice := strings.Split(out, "\n")
|
||||
c.Assert(outSlice, checker.HasLen, limit+2) // 1 header, 1 carriage return
|
||||
|
||||
limit = 50
|
||||
out, _, err = dockerCmdWithError("search", fmt.Sprintf("--limit=%d", limit), "docker")
|
||||
c.Assert(err, checker.IsNil)
|
||||
outSlice = strings.Split(out, "\n")
|
||||
c.Assert(outSlice, checker.HasLen, limit+2) // 1 header, 1 carriage return
|
||||
|
||||
limit = 100
|
||||
out, _, err = dockerCmdWithError("search", fmt.Sprintf("--limit=%d", limit), "docker")
|
||||
c.Assert(err, checker.IsNil)
|
||||
outSlice = strings.Split(out, "\n")
|
||||
c.Assert(outSlice, checker.HasLen, limit+2) // 1 header, 1 carriage return
|
||||
|
||||
limit = 0
|
||||
out, _, err = dockerCmdWithError("search", fmt.Sprintf("--limit=%d", limit), "docker")
|
||||
c.Assert(err, checker.Not(checker.IsNil))
|
||||
|
||||
limit = 200
|
||||
out, _, err = dockerCmdWithError("search", fmt.Sprintf("--limit=%d", limit), "docker")
|
||||
c.Assert(err, checker.Not(checker.IsNil))
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ docker-search - Search the Docker Hub for images
|
|||
**docker search**
|
||||
[**-f**|**--filter**[=*[]*]]
|
||||
[**--help**]
|
||||
[**--limit**[=*LIMIT*]]
|
||||
[**--no-trunc**]
|
||||
TERM
|
||||
|
||||
|
@ -30,6 +31,9 @@ of stars awarded, whether the image is official, and whether it is automated.
|
|||
**--help**
|
||||
Print usage statement
|
||||
|
||||
**--limit**=*LIMIT*
|
||||
Maximum returned search results. The default is 25.
|
||||
|
||||
**--no-trunc**=*true*|*false*
|
||||
Don't truncate output. The default is *false*.
|
||||
|
||||
|
|
|
@ -730,7 +730,7 @@ func TestPushImageJSONIndex(t *testing.T) {
|
|||
|
||||
func TestSearchRepositories(t *testing.T) {
|
||||
r := spawnTestRegistrySession(t)
|
||||
results, err := r.SearchRepositories("fakequery")
|
||||
results, err := r.SearchRepositories("fakequery", 25)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -15,6 +15,11 @@ import (
|
|||
registrytypes "github.com/docker/engine-api/types/registry"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultSearchLimit is the default value for maximum number of returned search results.
|
||||
DefaultSearchLimit = 25
|
||||
)
|
||||
|
||||
// Service is the interface defining what a registry service should implement.
|
||||
type Service interface {
|
||||
Auth(ctx context.Context, authConfig *types.AuthConfig, userAgent string) (status, token string, err error)
|
||||
|
@ -22,7 +27,7 @@ type Service interface {
|
|||
LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error)
|
||||
ResolveRepository(name reference.Named) (*RepositoryInfo, error)
|
||||
ResolveIndex(name string) (*registrytypes.IndexInfo, error)
|
||||
Search(ctx context.Context, term string, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error)
|
||||
Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error)
|
||||
ServiceConfig() *registrytypes.ServiceConfig
|
||||
TLSConfig(hostname string) (*tls.Config, error)
|
||||
}
|
||||
|
@ -108,7 +113,7 @@ func splitReposSearchTerm(reposName string) (string, string) {
|
|||
|
||||
// Search queries the public registry for images matching the specified
|
||||
// search terms, and returns the results.
|
||||
func (s *DefaultService) Search(ctx context.Context, term string, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) {
|
||||
func (s *DefaultService) Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) {
|
||||
// TODO Use ctx when searching for repositories
|
||||
if err := validateNoScheme(term); err != nil {
|
||||
return nil, err
|
||||
|
@ -139,9 +144,9 @@ func (s *DefaultService) Search(ctx context.Context, term string, authConfig *ty
|
|||
localName = strings.SplitN(localName, "/", 2)[1]
|
||||
}
|
||||
|
||||
return r.SearchRepositories(localName)
|
||||
return r.SearchRepositories(localName, limit)
|
||||
}
|
||||
return r.SearchRepositories(remoteName)
|
||||
return r.SearchRepositories(remoteName, limit)
|
||||
}
|
||||
|
||||
// ResolveRepository splits a repository name into its components
|
||||
|
|
|
@ -721,9 +721,12 @@ func shouldRedirect(response *http.Response) bool {
|
|||
}
|
||||
|
||||
// SearchRepositories performs a search against the remote repository
|
||||
func (r *Session) SearchRepositories(term string) (*registrytypes.SearchResults, error) {
|
||||
func (r *Session) SearchRepositories(term string, limit int) (*registrytypes.SearchResults, error) {
|
||||
if limit < 1 || limit > 100 {
|
||||
return nil, fmt.Errorf("Limit %d is outside the range of [1, 100]", limit)
|
||||
}
|
||||
logrus.Debugf("Index server: %s", r.indexEndpoint)
|
||||
u := r.indexEndpoint.String() + "search?q=" + url.QueryEscape(term)
|
||||
u := r.indexEndpoint.String() + "search?q=" + url.QueryEscape(term) + "&n=" + url.QueryEscape(fmt.Sprintf("%d", limit))
|
||||
|
||||
req, err := http.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in a new issue