Browse code

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>

Fabrizio Soppelsa authored on 2016/05/20 20:41:28
Showing 12 changed files
... ...
@@ -10,10 +10,12 @@ import (
10 10
 	"golang.org/x/net/context"
11 11
 
12 12
 	Cli "github.com/docker/docker/cli"
13
+	"github.com/docker/docker/opts"
13 14
 	flag "github.com/docker/docker/pkg/mflag"
14 15
 	"github.com/docker/docker/pkg/stringutils"
15 16
 	"github.com/docker/docker/registry"
16 17
 	"github.com/docker/engine-api/types"
18
+	"github.com/docker/engine-api/types/filters"
17 19
 	registrytypes "github.com/docker/engine-api/types/registry"
18 20
 )
19 21
 
... ...
@@ -21,14 +23,32 @@ import (
21 21
 //
22 22
 // Usage: docker search [OPTIONS] TERM
23 23
 func (cli *DockerCli) CmdSearch(args ...string) error {
24
+	var (
25
+		err error
26
+
27
+		filterArgs = filters.NewArgs()
28
+
29
+		flFilter = opts.NewListOpts(nil)
30
+	)
31
+
24 32
 	cmd := Cli.Subcmd("search", []string{"TERM"}, Cli.DockerCommands["search"].Description, true)
25 33
 	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")
34
+	cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
35
+
36
+	// Deprecated since Docker 1.12 in favor of "--filter"
37
+	automated := cmd.Bool([]string{"#-automated"}, false, "Only show automated builds - DEPRECATED")
38
+	stars := cmd.Uint([]string{"s", "#-stars"}, 0, "Only displays with at least x stars - DEPRECATED")
39
+
28 40
 	cmd.Require(flag.Exact, 1)
29 41
 
30 42
 	cmd.ParseFlags(args, true)
31 43
 
44
+	for _, f := range flFilter.GetAll() {
45
+		if filterArgs, err = filters.ParseFlag(f, filterArgs); err != nil {
46
+			return err
47
+		}
48
+	}
49
+
32 50
 	name := cmd.Arg(0)
33 51
 	v := url.Values{}
34 52
 	v.Set("term", name)
... ...
@@ -49,6 +69,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
49 49
 	options := types.ImageSearchOptions{
50 50
 		RegistryAuth:  encodedAuth,
51 51
 		PrivilegeFunc: requestPrivilege,
52
+		Filters:       filterArgs,
52 53
 	}
53 54
 
54 55
 	unorderedResults, err := cli.client.ImageSearch(context.Background(), name, options)
... ...
@@ -62,6 +83,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
62 62
 	w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0)
63 63
 	fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n")
64 64
 	for _, res := range results {
65
+		// --automated and -s, --stars are deprecated since Docker 1.12
65 66
 		if (*automated && !res.IsAutomated) || (int(*stars) > res.StarCount) {
66 67
 			continue
67 68
 		}
... ...
@@ -39,5 +39,5 @@ type importExportBackend interface {
39 39
 type registryBackend interface {
40 40
 	PullImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
41 41
 	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)
43 43
 }
... ...
@@ -301,7 +301,7 @@ func (s *imageRouter) getImagesSearch(ctx context.Context, w http.ResponseWriter
301 301
 			headers[k] = v
302 302
 		}
303 303
 	}
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)
305 305
 	if err != nil {
306 306
 		return err
307 307
 	}
... ...
@@ -1907,15 +1907,29 @@ _docker_save() {
1907 1907
 }
1908 1908
 
1909 1909
 _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
+
1910 1922
 	case "$prev" in
1911
-		--stars|-s)
1923
+		--filter|-f)
1924
+			COMPREPLY=( $( compgen -S = -W "is-automated is-official stars" -- "$cur" ) )
1925
+			__docker_nospace
1912 1926
 			return
1913 1927
 			;;
1914 1928
 	esac
1915 1929
 
1916 1930
 	case "$cur" in
1917 1931
 		-*)
1918
-			COMPREPLY=( $( compgen -W "--automated --help --no-trunc --stars -s" -- "$cur" ) )
1932
+			COMPREPLY=( $( compgen -W "--filter --help --no-trunc" -- "$cur" ) )
1919 1933
 			;;
1920 1934
 	esac
1921 1935
 }
... ...
@@ -311,6 +311,30 @@ __docker_complete_ps_filters() {
311 311
     return ret
312 312
 }
313 313
 
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
+
314 338
 __docker_network_complete_ls_filters() {
315 339
     [[ $PREFIX = -* ]] && return 1
316 340
     integer ret=1
... ...
@@ -1126,10 +1150,15 @@ __docker_subcommand() {
1126 1126
         (search)
1127 1127
             _arguments $(__docker_arguments) \
1128 1128
                 $opts_help \
1129
-                "($help)--automated[Only show automated builds]" \
1129
+                "($help)*"{-f=,--filter=}"[Filter values]:filter:->filter-options" \
1130 1130
                 "($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)" \
1132 1131
                 "($help -):term: " && ret=0
1132
+
1133
+            case $state in
1134
+                (filter-options)
1135
+                    __docker_complete_search_filters && ret=0
1136
+                    ;;
1137
+            esac
1133 1138
             ;;
1134 1139
         (start)
1135 1140
             _arguments $(__docker_arguments) \
... ...
@@ -15,6 +15,7 @@ import (
15 15
 	"path/filepath"
16 16
 	"regexp"
17 17
 	"runtime"
18
+	"strconv"
18 19
 	"strings"
19 20
 	"sync"
20 21
 	"syscall"
... ...
@@ -64,6 +65,7 @@ import (
64 64
 	volumedrivers "github.com/docker/docker/volume/drivers"
65 65
 	"github.com/docker/docker/volume/local"
66 66
 	"github.com/docker/docker/volume/store"
67
+	"github.com/docker/engine-api/types/filters"
67 68
 	"github.com/docker/go-connections/nat"
68 69
 	"github.com/docker/libnetwork"
69 70
 	nwconfig "github.com/docker/libnetwork/config"
... ...
@@ -1427,12 +1429,85 @@ func (daemon *Daemon) AuthenticateToRegistry(ctx context.Context, authConfig *ty
1427 1427
 	return daemon.RegistryService.Auth(authConfig, dockerversion.DockerUserAgent(ctx))
1428 1428
 }
1429 1429
 
1430
+var acceptedSearchFilterTags = map[string]bool{
1431
+	"is-automated": true,
1432
+	"is-official":  true,
1433
+	"stars":        true,
1434
+}
1435
+
1430 1436
 // SearchRegistryForImages queries the registry for images matching
1431 1437
 // term. authConfig is used to login.
1432
-func (daemon *Daemon) SearchRegistryForImages(ctx context.Context, term string,
1438
+func (daemon *Daemon) SearchRegistryForImages(ctx context.Context, filtersArgs string, term string,
1433 1439
 	authConfig *types.AuthConfig,
1434 1440
 	headers map[string][]string) (*registrytypes.SearchResults, error) {
1435
-	return daemon.RegistryService.Search(term, authConfig, dockerversion.DockerUserAgent(ctx), headers)
1441
+
1442
+	searchFilters, err := filters.FromParam(filtersArgs)
1443
+	if err != nil {
1444
+		return nil, err
1445
+	}
1446
+	if err := searchFilters.Validate(acceptedSearchFilterTags); err != nil {
1447
+		return nil, err
1448
+	}
1449
+
1450
+	unfilteredResult, err := daemon.RegistryService.Search(term, authConfig, dockerversion.DockerUserAgent(ctx), headers)
1451
+	if err != nil {
1452
+		return nil, err
1453
+	}
1454
+
1455
+	var isAutomated, isOfficial bool
1456
+	var hasStarFilter = 0
1457
+	if searchFilters.Include("is-automated") {
1458
+		if searchFilters.ExactMatch("is-automated", "true") {
1459
+			isAutomated = true
1460
+		} else if !searchFilters.ExactMatch("is-automated", "false") {
1461
+			return nil, fmt.Errorf("Invalid filter 'is-automated=%s'", searchFilters.Get("is-automated"))
1462
+		}
1463
+	}
1464
+	if searchFilters.Include("is-official") {
1465
+		if searchFilters.ExactMatch("is-official", "true") {
1466
+			isOfficial = true
1467
+		} else if !searchFilters.ExactMatch("is-official", "false") {
1468
+			return nil, fmt.Errorf("Invalid filter 'is-official=%s'", searchFilters.Get("is-official"))
1469
+		}
1470
+	}
1471
+	if searchFilters.Include("stars") {
1472
+		hasStars := searchFilters.Get("stars")
1473
+		for _, hasStar := range hasStars {
1474
+			iHasStar, err := strconv.Atoi(hasStar)
1475
+			if err != nil {
1476
+				return nil, fmt.Errorf("Invalid filter 'stars=%s'", hasStar)
1477
+			}
1478
+			if iHasStar > hasStarFilter {
1479
+				hasStarFilter = iHasStar
1480
+			}
1481
+		}
1482
+	}
1483
+
1484
+	filteredResults := []registrytypes.SearchResult{}
1485
+	for _, result := range unfilteredResult.Results {
1486
+		if searchFilters.Include("is-automated") {
1487
+			if isAutomated != result.IsAutomated {
1488
+				continue
1489
+			}
1490
+		}
1491
+		if searchFilters.Include("is-official") {
1492
+			if isOfficial != result.IsOfficial {
1493
+				continue
1494
+			}
1495
+		}
1496
+		if searchFilters.Include("stars") {
1497
+			if result.StarCount < hasStarFilter {
1498
+				continue
1499
+			}
1500
+		}
1501
+		filteredResults = append(filteredResults, result)
1502
+	}
1503
+
1504
+	return &registrytypes.SearchResults{
1505
+		Query:      unfilteredResult.Query,
1506
+		NumResults: len(filteredResults),
1507
+		Results:    filteredResults,
1508
+	}, nil
1436 1509
 }
1437 1510
 
1438 1511
 // IsShuttingDown tells whether the daemon is shutting down or not
... ...
@@ -58,6 +58,15 @@ defining it at container creation (`POST /containers/create`).
58 58
 The `docker ps --before` and `docker ps --since` options are deprecated.
59 59
 Use `docker ps --filter=before=...` and `docker ps --filter=since=...` instead.
60 60
 
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
+
61 70
 ### Command line short variant options
62 71
 **Deprecated In Release: v1.9**
63 72
 
... ...
@@ -118,6 +118,7 @@ This section lists each version from latest to oldest.  Each listing includes a
118 118
 * `POST /containers/create` now takes `MaximumIOps` and `MaximumIOBps` fields. Windows daemon only.
119 119
 * `POST /containers/create` now returns a HTTP 400 "bad parameter" message
120 120
   if no command is specified (instead of a HTTP 500 "server error")
121
+* `GET /images/search` now takes a `filters` query parameter.
121 122
 
122 123
 ### v1.23 API changes
123 124
 
... ...
@@ -2133,6 +2133,10 @@ Search for an image on [Docker Hub](https://hub.docker.com).
2133 2133
 Query Parameters:
2134 2134
 
2135 2135
 -   **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)`
2136 2140
 
2137 2141
 Status Codes:
2138 2142
 
... ...
@@ -14,10 +14,12 @@ parent = "smn_cli"
14 14
 
15 15
     Search the Docker Hub for images
16 16
 
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
18 21
       --help               Print usage
19 22
       --no-trunc           Don't truncate output
20
-      -s, --stars=0        Only displays with at least x stars
21 23
 
22 24
 Search [Docker Hub](https://hub.docker.com) for images
23 25
 
... ...
@@ -61,37 +63,59 @@ This example displays images with a name containing 'busybox':
61 61
     scottabernethy/busybox                                                           0                    [OK]
62 62
     marclop/busybox-solr
63 63
 
64
-### Search images by name and number of stars (-s, --stars)
64
+### Display non-truncated description (--no-trunc)
65
+
66
+This example displays images with a name containing 'busybox',
67
+at least 3 stars and the description isn't truncated in the output:
68
+
69
+    $ docker search --stars=3 --no-trunc busybox
70
+    NAME                 DESCRIPTION                                                                               STARS     OFFICIAL   AUTOMATED
71
+    busybox              Busybox base image.                                                                       325       [OK]       
72
+    progrium/busybox                                                                                               50                   [OK]
73
+    radial/busyboxplus   Full-chain, Internet enabled, busybox made from scratch. Comes in git and cURL flavors.   8                    [OK]
74
+
75
+## Filtering
76
+
77
+The filtering flag (`-f` or `--filter`) format is a `key=value` pair. If there is more
78
+than one filter, then pass multiple flags (e.g. `--filter "foo=bar" --filter "bif=baz"`)
79
+
80
+The currently supported filters are:
81
+
82
+* stars (int - number of stars the image has)
83
+* is-automated (true|false) - is the image automated or not
84
+* is-official (true|false) - is the image official or not
85
+
86
+
87
+### stars
65 88
 
66 89
 This example displays images with a name containing 'busybox' and at
67 90
 least 3 stars:
68 91
 
69
-    $ docker search --stars=3 busybox
92
+    $ docker search --filter stars=3 busybox
70 93
     NAME                 DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
71 94
     busybox              Busybox base image.                             325       [OK]       
72 95
     progrium/busybox                                                     50                   [OK]
73 96
     radial/busyboxplus   Full-chain, Internet enabled, busybox made...   8                    [OK]
74 97
 
75 98
 
76
-### Search automated images (--automated)
99
+### is-automated
77 100
 
78
-This example displays images with a name containing 'busybox', at
79
-least 3 stars and are automated builds:
101
+This example displays images with a name containing 'busybox'
102
+and are automated builds:
80 103
 
81
-    $ docker search --stars=3 --automated busybox
104
+    $ docker search --filter is-automated busybox
82 105
     NAME                 DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
83 106
     progrium/busybox                                                     50                   [OK]
84 107
     radial/busyboxplus   Full-chain, Internet enabled, busybox made...   8                    [OK]
85 108
 
109
+### is-official
86 110
 
87
-### Display non-truncated description (--no-trunc)
111
+This example displays images with a name containing 'busybox', at least
112
+3 stars and are official builds:
88 113
 
89
-This example displays images with a name containing 'busybox',
90
-at least 3 stars and the description isn't truncated in the output:
114
+    $ docker search --filter "is-automated=true" --filter "stars=3" busybox
115
+    NAME                 DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
116
+    progrium/busybox                                                     50                   [OK]
117
+    radial/busyboxplus   Full-chain, Internet enabled, busybox made...   8                    [OK]
91 118
 
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]
97 119
 
... ...
@@ -16,34 +16,78 @@ func (s *DockerSuite) TestSearchOnCentralRegistry(c *check.C) {
16 16
 }
17 17
 
18 18
 func (s *DockerSuite) TestSearchStarsOptionWithWrongParameter(c *check.C) {
19
-	out, _, err := dockerCmdWithError("search", "--stars=a", "busybox")
19
+	out, _, err := dockerCmdWithError("search", "--filter", "stars=a", "busybox")
20
+	c.Assert(err, check.NotNil, check.Commentf(out))
21
+	c.Assert(out, checker.Contains, "Invalid filter", check.Commentf("couldn't find the invalid filter warning"))
22
+
23
+	out, _, err = dockerCmdWithError("search", "-f", "stars=a", "busybox")
24
+	c.Assert(err, check.NotNil, check.Commentf(out))
25
+	c.Assert(out, checker.Contains, "Invalid filter", check.Commentf("couldn't find the invalid filter warning"))
26
+
27
+	out, _, err = dockerCmdWithError("search", "-f", "is-automated=a", "busybox")
28
+	c.Assert(err, check.NotNil, check.Commentf(out))
29
+	c.Assert(out, checker.Contains, "Invalid filter", check.Commentf("couldn't find the invalid filter warning"))
30
+
31
+	out, _, err = dockerCmdWithError("search", "-f", "is-official=a", "busybox")
32
+	c.Assert(err, check.NotNil, check.Commentf(out))
33
+	c.Assert(out, checker.Contains, "Invalid filter", check.Commentf("couldn't find the invalid filter warning"))
34
+
35
+	// -s --stars deprecated since Docker 1.13
36
+	out, _, err = dockerCmdWithError("search", "--stars=a", "busybox")
20 37
 	c.Assert(err, check.NotNil, check.Commentf(out))
21 38
 	c.Assert(out, checker.Contains, "invalid value", check.Commentf("couldn't find the invalid value warning"))
22 39
 
40
+	// -s --stars deprecated since Docker 1.13
23 41
 	out, _, err = dockerCmdWithError("search", "-s=-1", "busybox")
24 42
 	c.Assert(err, check.NotNil, check.Commentf(out))
25 43
 	c.Assert(out, checker.Contains, "invalid value", check.Commentf("couldn't find the invalid value warning"))
26 44
 }
27 45
 
28 46
 func (s *DockerSuite) TestSearchCmdOptions(c *check.C) {
29
-	testRequires(c, Network)
47
+	testRequires(c, Network, DaemonIsLinux)
30 48
 
31 49
 	out, _ := dockerCmd(c, "search", "--help")
32 50
 	c.Assert(out, checker.Contains, "Usage:\tdocker search [OPTIONS] TERM")
33 51
 
34 52
 	outSearchCmd, _ := dockerCmd(c, "search", "busybox")
35 53
 	outSearchCmdNotrunc, _ := dockerCmd(c, "search", "--no-trunc=true", "busybox")
54
+
36 55
 	c.Assert(len(outSearchCmd) > len(outSearchCmdNotrunc), check.Equals, false, check.Commentf("The no-trunc option can't take effect."))
37 56
 
38
-	outSearchCmdautomated, _ := dockerCmd(c, "search", "--automated=true", "busybox") //The busybox is a busybox base image, not an AUTOMATED image.
57
+	outSearchCmdautomated, _ := dockerCmd(c, "search", "--filter", "is-automated=true", "busybox") //The busybox is a busybox base image, not an AUTOMATED image.
39 58
 	outSearchCmdautomatedSlice := strings.Split(outSearchCmdautomated, "\n")
40 59
 	for i := range outSearchCmdautomatedSlice {
41
-		c.Assert(strings.HasPrefix(outSearchCmdautomatedSlice[i], "busybox "), check.Equals, false, check.Commentf("The busybox is not an AUTOMATED image: %s", out))
60
+		c.Assert(strings.HasPrefix(outSearchCmdautomatedSlice[i], "busybox "), check.Equals, false, check.Commentf("The busybox is not an AUTOMATED image: %s", outSearchCmdautomated))
61
+	}
62
+
63
+	outSearchCmdNotOfficial, _ := dockerCmd(c, "search", "--filter", "is-official=false", "busybox") //The busybox is a busybox base image, official image.
64
+	outSearchCmdNotOfficialSlice := strings.Split(outSearchCmdNotOfficial, "\n")
65
+	for i := range outSearchCmdNotOfficialSlice {
66
+		c.Assert(strings.HasPrefix(outSearchCmdNotOfficialSlice[i], "busybox "), check.Equals, false, check.Commentf("The busybox is not an OFFICIAL image: %s", outSearchCmdNotOfficial))
42 67
 	}
43 68
 
44
-	outSearchCmdStars, _ := dockerCmd(c, "search", "-s=2", "busybox")
69
+	outSearchCmdOfficial, _ := dockerCmd(c, "search", "--filter", "is-official=true", "busybox") //The busybox is a busybox base image, official image.
70
+	outSearchCmdOfficialSlice := strings.Split(outSearchCmdOfficial, "\n")
71
+	c.Assert(outSearchCmdOfficialSlice, checker.HasLen, 3) // 1 header, 1 line, 1 carriage return
72
+	c.Assert(strings.HasPrefix(outSearchCmdOfficialSlice[1], "busybox "), check.Equals, true, check.Commentf("The busybox is an OFFICIAL image: %s", outSearchCmdNotOfficial))
73
+
74
+	outSearchCmdStars, _ := dockerCmd(c, "search", "--filter", "stars=2", "busybox")
45 75
 	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))
46 76
 
77
+	dockerCmd(c, "search", "--filter", "is-automated=true", "--filter", "stars=2", "--no-trunc=true", "busybox")
78
+
79
+	// --automated deprecated since Docker 1.13
80
+	outSearchCmdautomated1, _ := dockerCmd(c, "search", "--automated=true", "busybox") //The busybox is a busybox base image, not an AUTOMATED image.
81
+	outSearchCmdautomatedSlice1 := strings.Split(outSearchCmdautomated1, "\n")
82
+	for i := range outSearchCmdautomatedSlice1 {
83
+		c.Assert(strings.HasPrefix(outSearchCmdautomatedSlice1[i], "busybox "), check.Equals, false, check.Commentf("The busybox is not an AUTOMATED image: %s", outSearchCmdautomated))
84
+	}
85
+
86
+	// -s --stars deprecated since Docker 1.13
87
+	outSearchCmdStars1, _ := dockerCmd(c, "search", "--stars=2", "busybox")
88
+	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))
89
+
90
+	// -s --stars deprecated since Docker 1.13
47 91
 	dockerCmd(c, "search", "--stars=2", "--automated=true", "--no-trunc=true", "busybox")
48 92
 }
49 93
 
... ...
@@ -6,10 +6,9 @@ docker-search - Search the Docker Hub for images
6 6
 
7 7
 # SYNOPSIS
8 8
 **docker search**
9
-[**--automated**]
9
+[**-f**|**--filter**[=*[]*]]
10 10
 [**--help**]
11 11
 [**--no-trunc**]
12
-[**-s**|**--stars**[=*0*]]
13 12
 TERM
14 13
 
15 14
 # DESCRIPTION
... ...
@@ -21,8 +20,12 @@ of stars awarded, whether the image is official, and whether it is automated.
21 21
 *Note* - Search queries will only return up to 25 results
22 22
 
23 23
 # OPTIONS
24
-**--automated**=*true*|*false*
25
-   Only show automated builds. The default is *false*.
24
+
25
+**-f**, **--filter**=[]
26
+   Filter output based on these conditions:
27
+   - stars=<numberOfStar>
28
+   - is-automated=(true|false)
29
+   - is-official=(true|false)
26 30
 
27 31
 **--help**
28 32
   Print usage statement
... ...
@@ -30,9 +33,6 @@ of stars awarded, whether the image is official, and whether it is automated.
30 30
 **--no-trunc**=*true*|*false*
31 31
    Don't truncate output. The default is *false*.
32 32
 
33
-**-s**, **--stars**=*X*
34
-   Only displays with at least X stars. The default is zero.
35
-
36 33
 # EXAMPLES
37 34
 
38 35
 ## Search Docker Hub for ranked images
... ...
@@ -40,7 +40,7 @@ of stars awarded, whether the image is official, and whether it is automated.
40 40
 Search a registry for the term 'fedora' and only display those images
41 41
 ranked 3 or higher:
42 42
 
43
-    $ docker search -s 3 fedora
43
+    $ docker search --filter=stars=3 fedora
44 44
     NAME                  DESCRIPTION                                    STARS OFFICIAL  AUTOMATED
45 45
     mattdm/fedora         A basic Fedora image corresponding roughly...  50
46 46
     fedora                (Semi) Official Fedora base image.             38
... ...
@@ -52,7 +52,7 @@ ranked 3 or higher:
52 52
 Search Docker Hub for the term 'fedora' and only display automated images
53 53
 ranked 1 or higher:
54 54
 
55
-    $ docker search --automated -s 1 fedora
55
+    $ docker search --filter=is-automated=true --filter=stars=1 fedora
56 56
     NAME               DESCRIPTION                                     STARS OFFICIAL  AUTOMATED
57 57
     goldmann/wildfly   A WildFly application server running on a ...   3               [OK]
58 58
     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)
62 62
 based on docker.com source material and internal work.
63 63
 June 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
64 64
 April 2015, updated by Mary Anthony for v2 <mary@docker.com>
65
+April 2016, updated by Vincent Demeester <vincent@sbr.pm>
65 66