From e4abf485677cd1d93d584753e19545e6ec8c2335 Mon Sep 17 00:00:00 2001
From: David Calavera <david.calavera@gmail.com>
Date: Sun, 6 Dec 2015 19:29:15 -0500
Subject: [PATCH] Implement docker search with standalone client lib.

Signed-off-by: David Calavera <david.calavera@gmail.com>
---
 api/client/client.go           |  2 ++
 api/client/lib/image_search.go | 39 ++++++++++++++++++++++++++++++++++
 api/client/search.go           | 33 ++++++++++++++++------------
 3 files changed, 61 insertions(+), 13 deletions(-)
 create mode 100644 api/client/lib/image_search.go

diff --git a/api/client/client.go b/api/client/client.go
index e3be35beb6..52dd4e5cd3 100644
--- a/api/client/client.go
+++ b/api/client/client.go
@@ -11,6 +11,7 @@ import (
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/cliconfig"
 	"github.com/docker/docker/pkg/parsers/filters"
+	"github.com/docker/docker/registry"
 	"github.com/docker/docker/runconfig"
 )
 
@@ -56,6 +57,7 @@ type apiClient interface {
 	ImagePull(options types.ImagePullOptions, privilegeFunc lib.RequestPrivilegeFunc) (io.ReadCloser, error)
 	ImagePush(options types.ImagePushOptions, privilegeFunc lib.RequestPrivilegeFunc) (io.ReadCloser, error)
 	ImageRemove(options types.ImageRemoveOptions) ([]types.ImageDelete, error)
+	ImageSearch(options types.ImageSearchOptions, privilegeFunc lib.RequestPrivilegeFunc) ([]registry.SearchResult, error)
 	ImageSave(imageIDs []string) (io.ReadCloser, error)
 	ImageTag(options types.ImageTagOptions) error
 	Info() (types.Info, error)
diff --git a/api/client/lib/image_search.go b/api/client/lib/image_search.go
new file mode 100644
index 0000000000..9d4a9ce81a
--- /dev/null
+++ b/api/client/lib/image_search.go
@@ -0,0 +1,39 @@
+package lib
+
+import (
+	"encoding/json"
+	"net/http"
+	"net/url"
+
+	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/registry"
+)
+
+// ImageSearch makes the docker host to search by a term in a remote registry.
+// The list of results is not sorted in any fashion.
+func (cli *Client) ImageSearch(options types.ImageSearchOptions, privilegeFunc RequestPrivilegeFunc) ([]registry.SearchResult, error) {
+	var results []registry.SearchResult
+	query := url.Values{}
+	query.Set("term", options.Term)
+
+	resp, err := cli.tryImageSearch(query, options.RegistryAuth)
+	if resp.statusCode == http.StatusUnauthorized {
+		newAuthHeader, privilegeErr := privilegeFunc()
+		if privilegeErr != nil {
+			return results, privilegeErr
+		}
+		resp, err = cli.tryImageSearch(query, newAuthHeader)
+	}
+	if err != nil {
+		return results, err
+	}
+	defer ensureReaderClosed(resp)
+
+	err = json.NewDecoder(resp.body).Decode(&results)
+	return results, err
+}
+
+func (cli *Client) tryImageSearch(query url.Values, registryAuth string) (*serverResponse, error) {
+	headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
+	return cli.get("/images/search", query, headers)
+}
diff --git a/api/client/search.go b/api/client/search.go
index 1a47064477..fea66d55b7 100644
--- a/api/client/search.go
+++ b/api/client/search.go
@@ -1,26 +1,19 @@
 package client
 
 import (
-	"encoding/json"
 	"fmt"
 	"net/url"
 	"sort"
 	"strings"
 	"text/tabwriter"
 
+	"github.com/docker/docker/api/types"
 	Cli "github.com/docker/docker/cli"
 	flag "github.com/docker/docker/pkg/mflag"
 	"github.com/docker/docker/pkg/stringutils"
 	"github.com/docker/docker/registry"
 )
 
-// ByStars sorts search results in ascending order by number of stars.
-type ByStars []registry.SearchResult
-
-func (r ByStars) Len() int           { return len(r) }
-func (r ByStars) Swap(i, j int)      { r[i], r[j] = r[j], r[i] }
-func (r ByStars) Less(i, j int) bool { return r[i].StarCount < r[j].StarCount }
-
 // CmdSearch searches the Docker Hub for images.
 //
 // Usage: docker search [OPTIONS] TERM
@@ -42,19 +35,26 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
 		return err
 	}
 
-	rdr, _, err := cli.clientRequestAttemptLogin("GET", "/images/search?"+v.Encode(), nil, nil, indexInfo, "search")
+	authConfig := registry.ResolveAuthConfig(cli.configFile, indexInfo)
+	requestPrivilege := cli.registryAuthenticationPrivilegedFunc(indexInfo, "search")
+
+	encodedAuth, err := authConfig.EncodeToBase64()
 	if err != nil {
 		return err
 	}
 
-	defer rdr.Close()
+	options := types.ImageSearchOptions{
+		Term:         name,
+		RegistryAuth: encodedAuth,
+	}
 
-	results := ByStars{}
-	if err := json.NewDecoder(rdr).Decode(&results); err != nil {
+	unorderedResults, err := cli.client.ImageSearch(options, requestPrivilege)
+	if err != nil {
 		return err
 	}
 
-	sort.Sort(sort.Reverse(results))
+	results := searchResultsByStars(unorderedResults)
+	sort.Sort(results)
 
 	w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0)
 	fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n")
@@ -81,3 +81,10 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
 	w.Flush()
 	return nil
 }
+
+// SearchResultsByStars sorts search results in descending order by number of stars.
+type searchResultsByStars []registry.SearchResult
+
+func (r searchResultsByStars) Len() int           { return len(r) }
+func (r searchResultsByStars) Swap(i, j int)      { r[i], r[j] = r[j], r[i] }
+func (r searchResultsByStars) Less(i, j int) bool { return r[j].StarCount < r[i].StarCount }