Pārlūkot izejas kodu

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 9 gadi atpakaļ
vecāks
revīzija
e009ebdf4c

+ 24 - 2
api/client/search.go

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

+ 1 - 1
api/server/router/image/backend.go

@@ -39,5 +39,5 @@ type importExportBackend interface {
 type registryBackend interface {
 type registryBackend interface {
 	PullImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
 	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
 	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)
 }
 }

+ 1 - 1
api/server/router/image/image_routes.go

@@ -301,7 +301,7 @@ func (s *imageRouter) getImagesSearch(ctx context.Context, w http.ResponseWriter
 			headers[k] = v
 			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 {
 	if err != nil {
 		return err
 		return err
 	}
 	}

+ 16 - 2
contrib/completion/bash/docker

@@ -1907,15 +1907,29 @@ _docker_save() {
 }
 }
 
 
 _docker_search() {
 _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
 	case "$prev" in
-		--stars|-s)
+		--filter|-f)
+			COMPREPLY=( $( compgen -S = -W "is-automated is-official stars" -- "$cur" ) )
+			__docker_nospace
 			return
 			return
 			;;
 			;;
 	esac
 	esac
 
 
 	case "$cur" in
 	case "$cur" in
 		-*)
 		-*)
-			COMPREPLY=( $( compgen -W "--automated --help --no-trunc --stars -s" -- "$cur" ) )
+			COMPREPLY=( $( compgen -W "--filter --help --no-trunc" -- "$cur" ) )
 			;;
 			;;
 	esac
 	esac
 }
 }

+ 31 - 2
contrib/completion/zsh/_docker

@@ -311,6 +311,30 @@ __docker_complete_ps_filters() {
     return ret
     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() {
 __docker_network_complete_ls_filters() {
     [[ $PREFIX = -* ]] && return 1
     [[ $PREFIX = -* ]] && return 1
     integer ret=1
     integer ret=1
@@ -1126,10 +1150,15 @@ __docker_subcommand() {
         (search)
         (search)
             _arguments $(__docker_arguments) \
             _arguments $(__docker_arguments) \
                 $opts_help \
                 $opts_help \
-                "($help)--automated[Only show automated builds]" \
+                "($help)*"{-f=,--filter=}"[Filter values]:filter:->filter-options" \
                 "($help)--no-trunc[Do not truncate output]" \
                 "($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
                 "($help -):term: " && ret=0
+
+            case $state in
+                (filter-options)
+                    __docker_complete_search_filters && ret=0
+                    ;;
+            esac
             ;;
             ;;
         (start)
         (start)
             _arguments $(__docker_arguments) \
             _arguments $(__docker_arguments) \

+ 77 - 2
daemon/daemon.go

@@ -15,6 +15,7 @@ import (
 	"path/filepath"
 	"path/filepath"
 	"regexp"
 	"regexp"
 	"runtime"
 	"runtime"
+	"strconv"
 	"strings"
 	"strings"
 	"sync"
 	"sync"
 	"syscall"
 	"syscall"
@@ -64,6 +65,7 @@ import (
 	volumedrivers "github.com/docker/docker/volume/drivers"
 	volumedrivers "github.com/docker/docker/volume/drivers"
 	"github.com/docker/docker/volume/local"
 	"github.com/docker/docker/volume/local"
 	"github.com/docker/docker/volume/store"
 	"github.com/docker/docker/volume/store"
+	"github.com/docker/engine-api/types/filters"
 	"github.com/docker/go-connections/nat"
 	"github.com/docker/go-connections/nat"
 	"github.com/docker/libnetwork"
 	"github.com/docker/libnetwork"
 	nwconfig "github.com/docker/libnetwork/config"
 	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))
 	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
 // SearchRegistryForImages queries the registry for images matching
 // term. authConfig is used to login.
 // 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,
 	authConfig *types.AuthConfig,
 	headers map[string][]string) (*registrytypes.SearchResults, error) {
 	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
 // IsShuttingDown tells whether the daemon is shutting down or not

+ 9 - 0
docs/deprecated.md

@@ -58,6 +58,15 @@ defining it at container creation (`POST /containers/create`).
 The `docker ps --before` and `docker ps --since` options are deprecated.
 The `docker ps --before` and `docker ps --since` options are deprecated.
 Use `docker ps --filter=before=...` and `docker ps --filter=since=...` instead.
 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
 ### Command line short variant options
 **Deprecated In Release: v1.9**
 **Deprecated In Release: v1.9**
 
 

+ 1 - 0
docs/reference/api/docker_remote_api.md

@@ -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 takes `MaximumIOps` and `MaximumIOBps` fields. Windows daemon only.
 * `POST /containers/create` now returns a HTTP 400 "bad parameter" message
 * `POST /containers/create` now returns a HTTP 400 "bad parameter" message
   if no command is specified (instead of a HTTP 500 "server error")
   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
 ### v1.23 API changes
 
 

+ 4 - 0
docs/reference/api/docker_remote_api_v1.24.md

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

+ 40 - 16
docs/reference/commandline/search.md

@@ -14,10 +14,12 @@ parent = "smn_cli"
 
 
     Search the Docker Hub for images
     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
       --help               Print usage
       --no-trunc           Don't truncate output
       --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
 Search [Docker Hub](https://hub.docker.com) for images
 
 
@@ -61,37 +63,59 @@ This example displays images with a name containing 'busybox':
     scottabernethy/busybox                                                           0                    [OK]
     scottabernethy/busybox                                                           0                    [OK]
     marclop/busybox-solr
     marclop/busybox-solr
 
 
-### Search images by name and number of stars (-s, --stars)
+### Display non-truncated description (--no-trunc)
+
+This example displays images with a name containing 'busybox',
+at least 3 stars and the description isn't truncated in the output:
+
+    $ docker search --stars=3 --no-trunc busybox
+    NAME                 DESCRIPTION                                                                               STARS     OFFICIAL   AUTOMATED
+    busybox              Busybox base image.                                                                       325       [OK]       
+    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
 This example displays images with a name containing 'busybox' and at
 least 3 stars:
 least 3 stars:
 
 
-    $ docker search --stars=3 busybox
+    $ docker search --filter stars=3 busybox
     NAME                 DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
     NAME                 DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
     busybox              Busybox base image.                             325       [OK]       
     busybox              Busybox base image.                             325       [OK]       
     progrium/busybox                                                     50                   [OK]
     progrium/busybox                                                     50                   [OK]
     radial/busyboxplus   Full-chain, Internet enabled, busybox made...   8                    [OK]
     radial/busyboxplus   Full-chain, Internet enabled, busybox made...   8                    [OK]
 
 
 
 
-### Search automated images (--automated)
+### is-automated
 
 
-This example displays images with a name containing 'busybox', at
-least 3 stars and are automated builds:
+This example displays images with a name containing 'busybox'
+and are automated builds:
 
 
-    $ docker search --stars=3 --automated busybox
+    $ docker search --filter is-automated busybox
     NAME                 DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
     NAME                 DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
     progrium/busybox                                                     50                   [OK]
     progrium/busybox                                                     50                   [OK]
     radial/busyboxplus   Full-chain, Internet enabled, busybox made...   8                    [OK]
     radial/busyboxplus   Full-chain, Internet enabled, busybox made...   8                    [OK]
 
 
+### is-official
 
 
-### Display non-truncated description (--no-trunc)
+This example displays images with a name containing 'busybox', at least
+3 stars and are official builds:
 
 
-This example displays images with a name containing 'busybox',
-at least 3 stars and the description isn't truncated in the output:
+    $ 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]
 
 
-    $ docker search --stars=3 --no-trunc busybox
-    NAME                 DESCRIPTION                                                                               STARS     OFFICIAL   AUTOMATED
-    busybox              Busybox base image.                                                                       325       [OK]       
-    progrium/busybox                                                                                               50                   [OK]
-    radial/busyboxplus   Full-chain, Internet enabled, busybox made from scratch. Comes in git and cURL flavors.   8                    [OK]
 
 

+ 49 - 5
integration-cli/docker_cli_search_test.go

@@ -16,34 +16,78 @@ func (s *DockerSuite) TestSearchOnCentralRegistry(c *check.C) {
 }
 }
 
 
 func (s *DockerSuite) TestSearchStarsOptionWithWrongParameter(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(err, check.NotNil, check.Commentf(out))
 	c.Assert(out, checker.Contains, "invalid value", check.Commentf("couldn't find the invalid value warning"))
 	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")
 	out, _, err = dockerCmdWithError("search", "-s=-1", "busybox")
 	c.Assert(err, check.NotNil, check.Commentf(out))
 	c.Assert(err, check.NotNil, check.Commentf(out))
 	c.Assert(out, checker.Contains, "invalid value", check.Commentf("couldn't find the invalid value warning"))
 	c.Assert(out, checker.Contains, "invalid value", check.Commentf("couldn't find the invalid value warning"))
 }
 }
 
 
 func (s *DockerSuite) TestSearchCmdOptions(c *check.C) {
 func (s *DockerSuite) TestSearchCmdOptions(c *check.C) {
-	testRequires(c, Network)
+	testRequires(c, Network, DaemonIsLinux)
 
 
 	out, _ := dockerCmd(c, "search", "--help")
 	out, _ := dockerCmd(c, "search", "--help")
 	c.Assert(out, checker.Contains, "Usage:\tdocker search [OPTIONS] TERM")
 	c.Assert(out, checker.Contains, "Usage:\tdocker search [OPTIONS] TERM")
 
 
 	outSearchCmd, _ := dockerCmd(c, "search", "busybox")
 	outSearchCmd, _ := dockerCmd(c, "search", "busybox")
 	outSearchCmdNotrunc, _ := dockerCmd(c, "search", "--no-trunc=true", "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."))
 	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")
 	outSearchCmdautomatedSlice := strings.Split(outSearchCmdautomated, "\n")
 	for i := range outSearchCmdautomatedSlice {
 	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))
+	}
+
+	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))
 	}
 	}
 
 
-	outSearchCmdStars, _ := dockerCmd(c, "search", "-s=2", "busybox")
+	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))
 	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")
 	dockerCmd(c, "search", "--stars=2", "--automated=true", "--no-trunc=true", "busybox")
 }
 }
 
 

+ 10 - 9
man/docker-search.1.md

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