Add a --filter option to `docker search`

The filtering is made server-side, and the following filters are
supported:

* is-official (boolean)
* is-automated (boolean)
* has-stars (integer)

Signed-off-by: Fabrizio Soppelsa <fsoppelsa@mirantis.com>
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
This commit is contained in:
Fabrizio Soppelsa 2016-05-20 13:41:28 +02:00 коммит произвёл Vincent Demeester
Родитель 0e9009bae3
Коммит e009ebdf4c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 083CC6FD6EB699A3
12 изменённых файлов: 272 добавлений и 49 удалений

Просмотреть файл

@ -10,10 +10,12 @@ import (
"golang.org/x/net/context"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/opts"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/stringutils"
"github.com/docker/docker/registry"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/filters"
registrytypes "github.com/docker/engine-api/types/registry"
)
@ -21,14 +23,32 @@ import (
//
// Usage: docker search [OPTIONS] TERM
func (cli *DockerCli) CmdSearch(args ...string) error {
var (
err error
filterArgs = filters.NewArgs()
flFilter = opts.NewListOpts(nil)
)
cmd := Cli.Subcmd("search", []string{"TERM"}, Cli.DockerCommands["search"].Description, true)
noTrunc := cmd.Bool([]string{"-no-trunc"}, false, "Don't truncate output")
automated := cmd.Bool([]string{"-automated"}, false, "Only show automated builds")
stars := cmd.Uint([]string{"s", "-stars"}, 0, "Only displays with at least x stars")
cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
// Deprecated since Docker 1.12 in favor of "--filter"
automated := cmd.Bool([]string{"#-automated"}, false, "Only show automated builds - DEPRECATED")
stars := cmd.Uint([]string{"s", "#-stars"}, 0, "Only displays with at least x stars - DEPRECATED")
cmd.Require(flag.Exact, 1)
cmd.ParseFlags(args, true)
for _, f := range flFilter.GetAll() {
if filterArgs, err = filters.ParseFlag(f, filterArgs); err != nil {
return err
}
}
name := cmd.Arg(0)
v := url.Values{}
v.Set("term", name)
@ -49,6 +69,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
options := types.ImageSearchOptions{
RegistryAuth: encodedAuth,
PrivilegeFunc: requestPrivilege,
Filters: filterArgs,
}
unorderedResults, err := cli.client.ImageSearch(context.Background(), name, options)
@ -62,6 +83,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0)
fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n")
for _, res := range results {
// --automated and -s, --stars are deprecated since Docker 1.12
if (*automated && !res.IsAutomated) || (int(*stars) > res.StarCount) {
continue
}

Просмотреть файл

@ -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, term string, authConfig *types.AuthConfig, metaHeaders map[string][]string) (*registry.SearchResults, error)
SearchRegistryForImages(ctx context.Context, filtersArgs string, term string, authConfig *types.AuthConfig, metaHeaders map[string][]string) (*registry.SearchResults, error)
}

Просмотреть файл

@ -301,7 +301,7 @@ func (s *imageRouter) getImagesSearch(ctx context.Context, w http.ResponseWriter
headers[k] = v
}
}
query, err := s.backend.SearchRegistryForImages(ctx, r.Form.Get("term"), config, headers)
query, err := s.backend.SearchRegistryForImages(ctx, r.Form.Get("filters"), r.Form.Get("term"), config, headers)
if err != nil {
return err
}

Просмотреть файл

@ -1907,15 +1907,29 @@ _docker_save() {
}
_docker_search() {
local key=$(__docker_map_key_of_current_option '--filter|-f')
case "$key" in
is-automated)
COMPREPLY=( $( compgen -W "false true" -- "${cur##*=}" ) )
return
;;
is-official)
COMPREPLY=( $( compgen -W "false true" -- "${cur##*=}" ) )
return
;;
esac
case "$prev" in
--stars|-s)
--filter|-f)
COMPREPLY=( $( compgen -S = -W "is-automated is-official stars" -- "$cur" ) )
__docker_nospace
return
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--automated --help --no-trunc --stars -s" -- "$cur" ) )
COMPREPLY=( $( compgen -W "--filter --help --no-trunc" -- "$cur" ) )
;;
esac
}

Просмотреть файл

@ -311,6 +311,30 @@ __docker_complete_ps_filters() {
return ret
}
__docker_complete_search_filters() {
[[ $PREFIX = -* ]] && return 1
integer ret=1
declare -a boolean_opts opts
boolean_opts=('true' 'false')
opts=('is-automated' 'is-official' 'stars')
if compset -P '*='; then
case "${${words[-1]%=*}#*=}" in
(is-automated|is-official)
_describe -t boolean-filter-opts "filter options" boolean_opts && ret=0
;;
*)
_message 'value' && ret=0
;;
esac
else
_describe -t filter-opts "filter options" opts -qS "=" && ret=0
fi
return ret
}
__docker_network_complete_ls_filters() {
[[ $PREFIX = -* ]] && return 1
integer ret=1
@ -1126,10 +1150,15 @@ __docker_subcommand() {
(search)
_arguments $(__docker_arguments) \
$opts_help \
"($help)--automated[Only show automated builds]" \
"($help)*"{-f=,--filter=}"[Filter values]:filter:->filter-options" \
"($help)--no-trunc[Do not truncate output]" \
"($help -s --stars)"{-s=,--stars=}"[Only display with at least X stars]:stars:(0 10 100 1000)" \
"($help -):term: " && ret=0
case $state in
(filter-options)
__docker_complete_search_filters && ret=0
;;
esac
;;
(start)
_arguments $(__docker_arguments) \

Просмотреть файл

@ -15,6 +15,7 @@ import (
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"sync"
"syscall"
@ -64,6 +65,7 @@ import (
volumedrivers "github.com/docker/docker/volume/drivers"
"github.com/docker/docker/volume/local"
"github.com/docker/docker/volume/store"
"github.com/docker/engine-api/types/filters"
"github.com/docker/go-connections/nat"
"github.com/docker/libnetwork"
nwconfig "github.com/docker/libnetwork/config"
@ -1427,12 +1429,85 @@ func (daemon *Daemon) AuthenticateToRegistry(ctx context.Context, authConfig *ty
return daemon.RegistryService.Auth(authConfig, dockerversion.DockerUserAgent(ctx))
}
var acceptedSearchFilterTags = map[string]bool{
"is-automated": true,
"is-official": true,
"stars": true,
}
// SearchRegistryForImages queries the registry for images matching
// term. authConfig is used to login.
func (daemon *Daemon) SearchRegistryForImages(ctx context.Context, term string,
func (daemon *Daemon) SearchRegistryForImages(ctx context.Context, filtersArgs string, term string,
authConfig *types.AuthConfig,
headers map[string][]string) (*registrytypes.SearchResults, error) {
return daemon.RegistryService.Search(term, authConfig, dockerversion.DockerUserAgent(ctx), headers)
searchFilters, err := filters.FromParam(filtersArgs)
if err != nil {
return nil, err
}
if err := searchFilters.Validate(acceptedSearchFilterTags); err != nil {
return nil, err
}
unfilteredResult, err := daemon.RegistryService.Search(term, authConfig, dockerversion.DockerUserAgent(ctx), headers)
if err != nil {
return nil, err
}
var isAutomated, isOfficial bool
var hasStarFilter = 0
if searchFilters.Include("is-automated") {
if searchFilters.ExactMatch("is-automated", "true") {
isAutomated = true
} else if !searchFilters.ExactMatch("is-automated", "false") {
return nil, fmt.Errorf("Invalid filter 'is-automated=%s'", searchFilters.Get("is-automated"))
}
}
if searchFilters.Include("is-official") {
if searchFilters.ExactMatch("is-official", "true") {
isOfficial = true
} else if !searchFilters.ExactMatch("is-official", "false") {
return nil, fmt.Errorf("Invalid filter 'is-official=%s'", searchFilters.Get("is-official"))
}
}
if searchFilters.Include("stars") {
hasStars := searchFilters.Get("stars")
for _, hasStar := range hasStars {
iHasStar, err := strconv.Atoi(hasStar)
if err != nil {
return nil, fmt.Errorf("Invalid filter 'stars=%s'", hasStar)
}
if iHasStar > hasStarFilter {
hasStarFilter = iHasStar
}
}
}
filteredResults := []registrytypes.SearchResult{}
for _, result := range unfilteredResult.Results {
if searchFilters.Include("is-automated") {
if isAutomated != result.IsAutomated {
continue
}
}
if searchFilters.Include("is-official") {
if isOfficial != result.IsOfficial {
continue
}
}
if searchFilters.Include("stars") {
if result.StarCount < hasStarFilter {
continue
}
}
filteredResults = append(filteredResults, result)
}
return &registrytypes.SearchResults{
Query: unfilteredResult.Query,
NumResults: len(filteredResults),
Results: filteredResults,
}, nil
}
// IsShuttingDown tells whether the daemon is shutting down or not

Просмотреть файл

@ -58,6 +58,15 @@ defining it at container creation (`POST /containers/create`).
The `docker ps --before` and `docker ps --since` options are deprecated.
Use `docker ps --filter=before=...` and `docker ps --filter=since=...` instead.
### Docker search 'automated' and 'stars' options
**Deprecated in Release: [v1.12.0](https://github.com/docker/docker/releases/tag/v1.12.0)**
**Removed In Release: v1.14**
The `docker search --automated` and `docker search --stars` options are deprecated.
Use `docker search --filter=is-automated=...` and `docker search --filter=stars=...` instead.
### Command line short variant options
**Deprecated In Release: v1.9**

Просмотреть файл

@ -118,6 +118,7 @@ This section lists each version from latest to oldest. Each listing includes a
* `POST /containers/create` now takes `MaximumIOps` and `MaximumIOBps` fields. Windows daemon only.
* `POST /containers/create` now returns a HTTP 400 "bad parameter" message
if no command is specified (instead of a HTTP 500 "server error")
* `GET /images/search` now takes a `filters` query parameter.
### v1.23 API changes

Просмотреть файл

@ -2133,6 +2133,10 @@ Search for an image on [Docker Hub](https://hub.docker.com).
Query Parameters:
- **term** – term to search
- **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)`
- `is-official=(true|false)`
Status Codes:

Просмотреть файл

@ -14,10 +14,12 @@ parent = "smn_cli"
Search the Docker Hub for images
--automated Only show automated builds
--filter=[] Filter output based on these conditions:
- is-automated=(true|false)
- is-official=(true|false)
- stars=<number> - image has at least 'number' stars
--help Print usage
--no-trunc Don't truncate output
-s, --stars=0 Only displays with at least x stars
Search [Docker Hub](https://hub.docker.com) for images
@ -61,29 +63,6 @@ This example displays images with a name containing 'busybox':
scottabernethy/busybox 0 [OK]
marclop/busybox-solr
### Search images by name and number of stars (-s, --stars)
This example displays images with a name containing 'busybox' and at
least 3 stars:
$ docker search --stars=3 busybox
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
busybox Busybox base image. 325 [OK]
progrium/busybox 50 [OK]
radial/busyboxplus Full-chain, Internet enabled, busybox made... 8 [OK]
### Search automated images (--automated)
This example displays images with a name containing 'busybox', at
least 3 stars and are automated builds:
$ docker search --stars=3 --automated busybox
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
progrium/busybox 50 [OK]
radial/busyboxplus Full-chain, Internet enabled, busybox made... 8 [OK]
### Display non-truncated description (--no-trunc)
This example displays images with a name containing 'busybox',
@ -95,3 +74,48 @@ 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]
## Filtering
The filtering flag (`-f` or `--filter`) format is a `key=value` pair. If there is more
than one filter, then pass multiple flags (e.g. `--filter "foo=bar" --filter "bif=baz"`)
The currently supported filters are:
* stars (int - number of stars the image has)
* is-automated (true|false) - is the image automated or not
* is-official (true|false) - is the image official or not
### stars
This example displays images with a name containing 'busybox' and at
least 3 stars:
$ docker search --filter stars=3 busybox
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
busybox Busybox base image. 325 [OK]
progrium/busybox 50 [OK]
radial/busyboxplus Full-chain, Internet enabled, busybox made... 8 [OK]
### is-automated
This example displays images with a name containing 'busybox'
and are automated builds:
$ docker search --filter is-automated busybox
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
progrium/busybox 50 [OK]
radial/busyboxplus Full-chain, Internet enabled, busybox made... 8 [OK]
### is-official
This example displays images with a name containing 'busybox', at least
3 stars and are official builds:
$ docker search --filter "is-automated=true" --filter "stars=3" busybox
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
progrium/busybox 50 [OK]
radial/busyboxplus Full-chain, Internet enabled, busybox made... 8 [OK]

Просмотреть файл

@ -16,34 +16,78 @@ func (s *DockerSuite) TestSearchOnCentralRegistry(c *check.C) {
}
func (s *DockerSuite) TestSearchStarsOptionWithWrongParameter(c *check.C) {
out, _, err := dockerCmdWithError("search", "--stars=a", "busybox")
out, _, err := dockerCmdWithError("search", "--filter", "stars=a", "busybox")
c.Assert(err, check.NotNil, check.Commentf(out))
c.Assert(out, checker.Contains, "Invalid filter", check.Commentf("couldn't find the invalid filter warning"))
out, _, err = dockerCmdWithError("search", "-f", "stars=a", "busybox")
c.Assert(err, check.NotNil, check.Commentf(out))
c.Assert(out, checker.Contains, "Invalid filter", check.Commentf("couldn't find the invalid filter warning"))
out, _, err = dockerCmdWithError("search", "-f", "is-automated=a", "busybox")
c.Assert(err, check.NotNil, check.Commentf(out))
c.Assert(out, checker.Contains, "Invalid filter", check.Commentf("couldn't find the invalid filter warning"))
out, _, err = dockerCmdWithError("search", "-f", "is-official=a", "busybox")
c.Assert(err, check.NotNil, check.Commentf(out))
c.Assert(out, checker.Contains, "Invalid filter", check.Commentf("couldn't find the invalid filter warning"))
// -s --stars deprecated since Docker 1.13
out, _, err = dockerCmdWithError("search", "--stars=a", "busybox")
c.Assert(err, check.NotNil, check.Commentf(out))
c.Assert(out, checker.Contains, "invalid value", check.Commentf("couldn't find the invalid value warning"))
// -s --stars deprecated since Docker 1.13
out, _, err = dockerCmdWithError("search", "-s=-1", "busybox")
c.Assert(err, check.NotNil, check.Commentf(out))
c.Assert(out, checker.Contains, "invalid value", check.Commentf("couldn't find the invalid value warning"))
}
func (s *DockerSuite) TestSearchCmdOptions(c *check.C) {
testRequires(c, Network)
testRequires(c, Network, DaemonIsLinux)
out, _ := dockerCmd(c, "search", "--help")
c.Assert(out, checker.Contains, "Usage:\tdocker search [OPTIONS] TERM")
outSearchCmd, _ := dockerCmd(c, "search", "busybox")
outSearchCmdNotrunc, _ := dockerCmd(c, "search", "--no-trunc=true", "busybox")
c.Assert(len(outSearchCmd) > len(outSearchCmdNotrunc), check.Equals, false, check.Commentf("The no-trunc option can't take effect."))
outSearchCmdautomated, _ := dockerCmd(c, "search", "--automated=true", "busybox") //The busybox is a busybox base image, not an AUTOMATED image.
outSearchCmdautomated, _ := dockerCmd(c, "search", "--filter", "is-automated=true", "busybox") //The busybox is a busybox base image, not an AUTOMATED image.
outSearchCmdautomatedSlice := strings.Split(outSearchCmdautomated, "\n")
for i := range outSearchCmdautomatedSlice {
c.Assert(strings.HasPrefix(outSearchCmdautomatedSlice[i], "busybox "), check.Equals, false, check.Commentf("The busybox is not an AUTOMATED image: %s", out))
c.Assert(strings.HasPrefix(outSearchCmdautomatedSlice[i], "busybox "), check.Equals, false, check.Commentf("The busybox is not an AUTOMATED image: %s", outSearchCmdautomated))
}
outSearchCmdStars, _ := dockerCmd(c, "search", "-s=2", "busybox")
outSearchCmdNotOfficial, _ := dockerCmd(c, "search", "--filter", "is-official=false", "busybox") //The busybox is a busybox base image, official image.
outSearchCmdNotOfficialSlice := strings.Split(outSearchCmdNotOfficial, "\n")
for i := range outSearchCmdNotOfficialSlice {
c.Assert(strings.HasPrefix(outSearchCmdNotOfficialSlice[i], "busybox "), check.Equals, false, check.Commentf("The busybox is not an OFFICIAL image: %s", outSearchCmdNotOfficial))
}
outSearchCmdOfficial, _ := dockerCmd(c, "search", "--filter", "is-official=true", "busybox") //The busybox is a busybox base image, official image.
outSearchCmdOfficialSlice := strings.Split(outSearchCmdOfficial, "\n")
c.Assert(outSearchCmdOfficialSlice, checker.HasLen, 3) // 1 header, 1 line, 1 carriage return
c.Assert(strings.HasPrefix(outSearchCmdOfficialSlice[1], "busybox "), check.Equals, true, check.Commentf("The busybox is an OFFICIAL image: %s", outSearchCmdNotOfficial))
outSearchCmdStars, _ := dockerCmd(c, "search", "--filter", "stars=2", "busybox")
c.Assert(strings.Count(outSearchCmdStars, "[OK]") > strings.Count(outSearchCmd, "[OK]"), check.Equals, false, check.Commentf("The quantity of images with stars should be less than that of all images: %s", outSearchCmdStars))
dockerCmd(c, "search", "--filter", "is-automated=true", "--filter", "stars=2", "--no-trunc=true", "busybox")
// --automated deprecated since Docker 1.13
outSearchCmdautomated1, _ := dockerCmd(c, "search", "--automated=true", "busybox") //The busybox is a busybox base image, not an AUTOMATED image.
outSearchCmdautomatedSlice1 := strings.Split(outSearchCmdautomated1, "\n")
for i := range outSearchCmdautomatedSlice1 {
c.Assert(strings.HasPrefix(outSearchCmdautomatedSlice1[i], "busybox "), check.Equals, false, check.Commentf("The busybox is not an AUTOMATED image: %s", outSearchCmdautomated))
}
// -s --stars deprecated since Docker 1.13
outSearchCmdStars1, _ := dockerCmd(c, "search", "--stars=2", "busybox")
c.Assert(strings.Count(outSearchCmdStars1, "[OK]") > strings.Count(outSearchCmd, "[OK]"), check.Equals, false, check.Commentf("The quantity of images with stars should be less than that of all images: %s", outSearchCmdStars1))
// -s --stars deprecated since Docker 1.13
dockerCmd(c, "search", "--stars=2", "--automated=true", "--no-trunc=true", "busybox")
}

Просмотреть файл

@ -6,10 +6,9 @@ docker-search - Search the Docker Hub for images
# SYNOPSIS
**docker search**
[**--automated**]
[**-f**|**--filter**[=*[]*]]
[**--help**]
[**--no-trunc**]
[**-s**|**--stars**[=*0*]]
TERM
# DESCRIPTION
@ -21,8 +20,12 @@ of stars awarded, whether the image is official, and whether it is automated.
*Note* - Search queries will only return up to 25 results
# OPTIONS
**--automated**=*true*|*false*
Only show automated builds. The default is *false*.
**-f**, **--filter**=[]
Filter output based on these conditions:
- stars=<numberOfStar>
- is-automated=(true|false)
- is-official=(true|false)
**--help**
Print usage statement
@ -30,9 +33,6 @@ of stars awarded, whether the image is official, and whether it is automated.
**--no-trunc**=*true*|*false*
Don't truncate output. The default is *false*.
**-s**, **--stars**=*X*
Only displays with at least X stars. The default is zero.
# EXAMPLES
## Search Docker Hub for ranked images
@ -40,7 +40,7 @@ of stars awarded, whether the image is official, and whether it is automated.
Search a registry for the term 'fedora' and only display those images
ranked 3 or higher:
$ docker search -s 3 fedora
$ docker search --filter=stars=3 fedora
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
mattdm/fedora A basic Fedora image corresponding roughly... 50
fedora (Semi) Official Fedora base image. 38
@ -52,7 +52,7 @@ ranked 3 or higher:
Search Docker Hub for the term 'fedora' and only display automated images
ranked 1 or higher:
$ docker search --automated -s 1 fedora
$ docker search --filter=is-automated=true --filter=stars=1 fedora
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
goldmann/wildfly A WildFly application server running on a ... 3 [OK]
tutum/fedora-20 Fedora 20 image with SSH access. For the r... 1 [OK]
@ -62,4 +62,5 @@ April 2014, Originally compiled by William Henry (whenry at redhat dot com)
based on docker.com source material and internal work.
June 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
April 2015, updated by Mary Anthony for v2 <mary@docker.com>
April 2016, updated by Vincent Demeester <vincent@sbr.pm>