Skip to content

Commit e009ebd

Browse files
Fabrizio Soppelsavdemeester
authored andcommitted
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>
1 parent 0e9009b commit e009ebd

File tree

12 files changed

+263
-40
lines changed

12 files changed

+263
-40
lines changed

api/client/search.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,45 @@ import (
1010
"golang.org/x/net/context"
1111

1212
Cli "github.com/docker/docker/cli"
13+
"github.com/docker/docker/opts"
1314
flag "github.com/docker/docker/pkg/mflag"
1415
"github.com/docker/docker/pkg/stringutils"
1516
"github.com/docker/docker/registry"
1617
"github.com/docker/engine-api/types"
18+
"github.com/docker/engine-api/types/filters"
1719
registrytypes "github.com/docker/engine-api/types/registry"
1820
)
1921

2022
// CmdSearch searches the Docker Hub for images.
2123
//
2224
// Usage: docker search [OPTIONS] TERM
2325
func (cli *DockerCli) CmdSearch(args ...string) error {
26+
var (
27+
err error
28+
29+
filterArgs = filters.NewArgs()
30+
31+
flFilter = opts.NewListOpts(nil)
32+
)
33+
2434
cmd := Cli.Subcmd("search", []string{"TERM"}, Cli.DockerCommands["search"].Description, true)
2535
noTrunc := cmd.Bool([]string{"-no-trunc"}, false, "Don't truncate output")
26-
automated := cmd.Bool([]string{"-automated"}, false, "Only show automated builds")
27-
stars := cmd.Uint([]string{"s", "-stars"}, 0, "Only displays with at least x stars")
36+
cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
37+
38+
// Deprecated since Docker 1.12 in favor of "--filter"
39+
automated := cmd.Bool([]string{"#-automated"}, false, "Only show automated builds - DEPRECATED")
40+
stars := cmd.Uint([]string{"s", "#-stars"}, 0, "Only displays with at least x stars - DEPRECATED")
41+
2842
cmd.Require(flag.Exact, 1)
2943

3044
cmd.ParseFlags(args, true)
3145

46+
for _, f := range flFilter.GetAll() {
47+
if filterArgs, err = filters.ParseFlag(f, filterArgs); err != nil {
48+
return err
49+
}
50+
}
51+
3252
name := cmd.Arg(0)
3353
v := url.Values{}
3454
v.Set("term", name)
@@ -49,6 +69,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
4969
options := types.ImageSearchOptions{
5070
RegistryAuth: encodedAuth,
5171
PrivilegeFunc: requestPrivilege,
72+
Filters: filterArgs,
5273
}
5374

5475
unorderedResults, err := cli.client.ImageSearch(context.Background(), name, options)
@@ -62,6 +83,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
6283
w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0)
6384
fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n")
6485
for _, res := range results {
86+
// --automated and -s, --stars are deprecated since Docker 1.12
6587
if (*automated && !res.IsAutomated) || (int(*stars) > res.StarCount) {
6688
continue
6789
}

api/server/router/image/backend.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,5 @@ type importExportBackend interface {
3939
type registryBackend interface {
4040
PullImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
4141
PushImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
42-
SearchRegistryForImages(ctx context.Context, term string, authConfig *types.AuthConfig, metaHeaders map[string][]string) (*registry.SearchResults, error)
42+
SearchRegistryForImages(ctx context.Context, filtersArgs string, term string, authConfig *types.AuthConfig, metaHeaders map[string][]string) (*registry.SearchResults, error)
4343
}

api/server/router/image/image_routes.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ func (s *imageRouter) getImagesSearch(ctx context.Context, w http.ResponseWriter
301301
headers[k] = v
302302
}
303303
}
304-
query, err := s.backend.SearchRegistryForImages(ctx, r.Form.Get("term"), config, headers)
304+
query, err := s.backend.SearchRegistryForImages(ctx, r.Form.Get("filters"), r.Form.Get("term"), config, headers)
305305
if err != nil {
306306
return err
307307
}

contrib/completion/bash/docker

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1907,15 +1907,29 @@ _docker_save() {
19071907
}
19081908

19091909
_docker_search() {
1910+
local key=$(__docker_map_key_of_current_option '--filter|-f')
1911+
case "$key" in
1912+
is-automated)
1913+
COMPREPLY=( $( compgen -W "false true" -- "${cur##*=}" ) )
1914+
return
1915+
;;
1916+
is-official)
1917+
COMPREPLY=( $( compgen -W "false true" -- "${cur##*=}" ) )
1918+
return
1919+
;;
1920+
esac
1921+
19101922
case "$prev" in
1911-
--stars|-s)
1923+
--filter|-f)
1924+
COMPREPLY=( $( compgen -S = -W "is-automated is-official stars" -- "$cur" ) )
1925+
__docker_nospace
19121926
return
19131927
;;
19141928
esac
19151929

19161930
case "$cur" in
19171931
-*)
1918-
COMPREPLY=( $( compgen -W "--automated --help --no-trunc --stars -s" -- "$cur" ) )
1932+
COMPREPLY=( $( compgen -W "--filter --help --no-trunc" -- "$cur" ) )
19191933
;;
19201934
esac
19211935
}

contrib/completion/zsh/_docker

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,30 @@ __docker_complete_ps_filters() {
311311
return ret
312312
}
313313

314+
__docker_complete_search_filters() {
315+
[[ $PREFIX = -* ]] && return 1
316+
integer ret=1
317+
declare -a boolean_opts opts
318+
319+
boolean_opts=('true' 'false')
320+
opts=('is-automated' 'is-official' 'stars')
321+
322+
if compset -P '*='; then
323+
case "${${words[-1]%=*}#*=}" in
324+
(is-automated|is-official)
325+
_describe -t boolean-filter-opts "filter options" boolean_opts && ret=0
326+
;;
327+
*)
328+
_message 'value' && ret=0
329+
;;
330+
esac
331+
else
332+
_describe -t filter-opts "filter options" opts -qS "=" && ret=0
333+
fi
334+
335+
return ret
336+
}
337+
314338
__docker_network_complete_ls_filters() {
315339
[[ $PREFIX = -* ]] && return 1
316340
integer ret=1
@@ -1126,10 +1150,15 @@ __docker_subcommand() {
11261150
(search)
11271151
_arguments $(__docker_arguments) \
11281152
$opts_help \
1129-
"($help)--automated[Only show automated builds]" \
1153+
"($help)*"{-f=,--filter=}"[Filter values]:filter:->filter-options" \
11301154
"($help)--no-trunc[Do not truncate output]" \
1131-
"($help -s --stars)"{-s=,--stars=}"[Only display with at least X stars]:stars:(0 10 100 1000)" \
11321155
"($help -):term: " && ret=0
1156+
1157+
case $state in
1158+
(filter-options)
1159+
__docker_complete_search_filters && ret=0
1160+
;;
1161+
esac
11331162
;;
11341163
(start)
11351164
_arguments $(__docker_arguments) \

daemon/daemon.go

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"path/filepath"
1616
"regexp"
1717
"runtime"
18+
"strconv"
1819
"strings"
1920
"sync"
2021
"syscall"
@@ -64,6 +65,7 @@ import (
6465
volumedrivers "github.com/docker/docker/volume/drivers"
6566
"github.com/docker/docker/volume/local"
6667
"github.com/docker/docker/volume/store"
68+
"github.com/docker/engine-api/types/filters"
6769
"github.com/docker/go-connections/nat"
6870
"github.com/docker/libnetwork"
6971
nwconfig "github.com/docker/libnetwork/config"
@@ -1427,12 +1429,85 @@ func (daemon *Daemon) AuthenticateToRegistry(ctx context.Context, authConfig *ty
14271429
return daemon.RegistryService.Auth(authConfig, dockerversion.DockerUserAgent(ctx))
14281430
}
14291431

1432+
var acceptedSearchFilterTags = map[string]bool{
1433+
"is-automated": true,
1434+
"is-official": true,
1435+
"stars": true,
1436+
}
1437+
14301438
// SearchRegistryForImages queries the registry for images matching
14311439
// term. authConfig is used to login.
1432-
func (daemon *Daemon) SearchRegistryForImages(ctx context.Context, term string,
1440+
func (daemon *Daemon) SearchRegistryForImages(ctx context.Context, filtersArgs string, term string,
14331441
authConfig *types.AuthConfig,
14341442
headers map[string][]string) (*registrytypes.SearchResults, error) {
1435-
return daemon.RegistryService.Search(term, authConfig, dockerversion.DockerUserAgent(ctx), headers)
1443+
1444+
searchFilters, err := filters.FromParam(filtersArgs)
1445+
if err != nil {
1446+
return nil, err
1447+
}
1448+
if err := searchFilters.Validate(acceptedSearchFilterTags); err != nil {
1449+
return nil, err
1450+
}
1451+
1452+
unfilteredResult, err := daemon.RegistryService.Search(term, authConfig, dockerversion.DockerUserAgent(ctx), headers)
1453+
if err != nil {
1454+
return nil, err
1455+
}
1456+
1457+
var isAutomated, isOfficial bool
1458+
var hasStarFilter = 0
1459+
if searchFilters.Include("is-automated") {
1460+
if searchFilters.ExactMatch("is-automated", "true") {
1461+
isAutomated = true
1462+
} else if !searchFilters.ExactMatch("is-automated", "false") {
1463+
return nil, fmt.Errorf("Invalid filter 'is-automated=%s'", searchFilters.Get("is-automated"))
1464+
}
1465+
}
1466+
if searchFilters.Include("is-official") {
1467+
if searchFilters.ExactMatch("is-official", "true") {
1468+
isOfficial = true
1469+
} else if !searchFilters.ExactMatch("is-official", "false") {
1470+
return nil, fmt.Errorf("Invalid filter 'is-official=%s'", searchFilters.Get("is-official"))
1471+
}
1472+
}
1473+
if searchFilters.Include("stars") {
1474+
hasStars := searchFilters.Get("stars")
1475+
for _, hasStar := range hasStars {
1476+
iHasStar, err := strconv.Atoi(hasStar)
1477+
if err != nil {
1478+
return nil, fmt.Errorf("Invalid filter 'stars=%s'", hasStar)
1479+
}
1480+
if iHasStar > hasStarFilter {
1481+
hasStarFilter = iHasStar
1482+
}
1483+
}
1484+
}
1485+
1486+
filteredResults := []registrytypes.SearchResult{}
1487+
for _, result := range unfilteredResult.Results {
1488+
if searchFilters.Include("is-automated") {
1489+
if isAutomated != result.IsAutomated {
1490+
continue
1491+
}
1492+
}
1493+
if searchFilters.Include("is-official") {
1494+
if isOfficial != result.IsOfficial {
1495+
continue
1496+
}
1497+
}
1498+
if searchFilters.Include("stars") {
1499+
if result.StarCount < hasStarFilter {
1500+
continue
1501+
}
1502+
}
1503+
filteredResults = append(filteredResults, result)
1504+
}
1505+
1506+
return &registrytypes.SearchResults{
1507+
Query: unfilteredResult.Query,
1508+
NumResults: len(filteredResults),
1509+
Results: filteredResults,
1510+
}, nil
14361511
}
14371512

14381513
// IsShuttingDown tells whether the daemon is shutting down or not

docs/deprecated.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,15 @@ defining it at container creation (`POST /containers/create`).
5858
The `docker ps --before` and `docker ps --since` options are deprecated.
5959
Use `docker ps --filter=before=...` and `docker ps --filter=since=...` instead.
6060

61+
### Docker search 'automated' and 'stars' options
62+
63+
**Deprecated in Release: [v1.12.0](https://github.com/docker/docker/releases/tag/v1.12.0)**
64+
65+
**Removed In Release: v1.14**
66+
67+
The `docker search --automated` and `docker search --stars` options are deprecated.
68+
Use `docker search --filter=is-automated=...` and `docker search --filter=stars=...` instead.
69+
6170
### Command line short variant options
6271
**Deprecated In Release: v1.9**
6372

docs/reference/api/docker_remote_api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ This section lists each version from latest to oldest. Each listing includes a
118118
* `POST /containers/create` now takes `MaximumIOps` and `MaximumIOBps` fields. Windows daemon only.
119119
* `POST /containers/create` now returns a HTTP 400 "bad parameter" message
120120
if no command is specified (instead of a HTTP 500 "server error")
121+
* `GET /images/search` now takes a `filters` query parameter.
121122

122123
### v1.23 API changes
123124

docs/reference/api/docker_remote_api_v1.24.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2133,6 +2133,10 @@ Search for an image on [Docker Hub](https://hub.docker.com).
21332133
Query Parameters:
21342134
21352135
- **term** – term to search
2136+
- **filters** – a JSON encoded value of the filters (a map[string][]string) to process on the images list. Available filters:
2137+
- `stars=<number>`
2138+
- `is-automated=(true|false)`
2139+
- `is-official=(true|false)`
21362140
21372141
Status Codes:
21382142

docs/reference/commandline/search.md

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ parent = "smn_cli"
1414

1515
Search the Docker Hub for images
1616

17-
--automated Only show automated builds
17+
--filter=[] Filter output based on these conditions:
18+
- is-automated=(true|false)
19+
- is-official=(true|false)
20+
- stars=<number> - image has at least 'number' stars
1821
--help Print usage
1922
--no-trunc Don't truncate output
20-
-s, --stars=0 Only displays with at least x stars
2123

2224
Search [Docker Hub](https://hub.docker.com) for images
2325

@@ -61,37 +63,59 @@ This example displays images with a name containing 'busybox':
6163
scottabernethy/busybox 0 [OK]
6264
marclop/busybox-solr
6365

64-
### Search images by name and number of stars (-s, --stars)
66+
### Display non-truncated description (--no-trunc)
67+
68+
This example displays images with a name containing 'busybox',
69+
at least 3 stars and the description isn't truncated in the output:
70+
71+
$ docker search --stars=3 --no-trunc busybox
72+
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
73+
busybox Busybox base image. 325 [OK]
74+
progrium/busybox 50 [OK]
75+
radial/busyboxplus Full-chain, Internet enabled, busybox made from scratch. Comes in git and cURL flavors. 8 [OK]
76+
77+
## Filtering
78+
79+
The filtering flag (`-f` or `--filter`) format is a `key=value` pair. If there is more
80+
than one filter, then pass multiple flags (e.g. `--filter "foo=bar" --filter "bif=baz"`)
81+
82+
The currently supported filters are:
83+
84+
* stars (int - number of stars the image has)
85+
* is-automated (true|false) - is the image automated or not
86+
* is-official (true|false) - is the image official or not
87+
88+
89+
### stars
6590

6691
This example displays images with a name containing 'busybox' and at
6792
least 3 stars:
6893

69-
$ docker search --stars=3 busybox
94+
$ docker search --filter stars=3 busybox
7095
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
7196
busybox Busybox base image. 325 [OK]
7297
progrium/busybox 50 [OK]
7398
radial/busyboxplus Full-chain, Internet enabled, busybox made... 8 [OK]
7499

75100

76-
### Search automated images (--automated)
101+
### is-automated
77102

78-
This example displays images with a name containing 'busybox', at
79-
least 3 stars and are automated builds:
103+
This example displays images with a name containing 'busybox'
104+
and are automated builds:
80105

81-
$ docker search --stars=3 --automated busybox
106+
$ docker search --filter is-automated busybox
82107
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
83108
progrium/busybox 50 [OK]
84109
radial/busyboxplus Full-chain, Internet enabled, busybox made... 8 [OK]
85110

111+
### is-official
86112

87-
### Display non-truncated description (--no-trunc)
113+
This example displays images with a name containing 'busybox', at least
114+
3 stars and are official builds:
88115

89-
This example displays images with a name containing 'busybox',
90-
at least 3 stars and the description isn't truncated in the output:
116+
$ docker search --filter "is-automated=true" --filter "stars=3" busybox
117+
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
118+
progrium/busybox 50 [OK]
119+
radial/busyboxplus Full-chain, Internet enabled, busybox made... 8 [OK]
91120

92-
$ docker search --stars=3 --no-trunc busybox
93-
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
94-
busybox Busybox base image. 325 [OK]
95-
progrium/busybox 50 [OK]
96-
radial/busyboxplus Full-chain, Internet enabled, busybox made from scratch. Comes in git and cURL flavors. 8 [OK]
97121

0 commit comments

Comments
 (0)