Просмотр исходного кода

Use spf13/cobra for docker search

- Move image command search to `api/client/image/search.go`
- Use cobra :)

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
Vincent Demeester 9 лет назад
Родитель
Сommit
a11ef10631

+ 0 - 1
api/client/commands.go

@@ -40,7 +40,6 @@ func (cli *DockerCli) Command(name string) func(...string) error {
 		"rmi":                cli.CmdRmi,
 		"run":                cli.CmdRun,
 		"save":               cli.CmdSave,
-		"search":             cli.CmdSearch,
 		"start":              cli.CmdStart,
 		"stats":              cli.CmdStats,
 		"stop":               cli.CmdStop,

+ 2 - 2
api/client/create.go

@@ -31,8 +31,8 @@ func (cli *DockerCli) pullImage(ctx context.Context, image string, out io.Writer
 		return err
 	}
 
-	authConfig := cli.resolveAuthConfig(ctx, repoInfo.Index)
-	encodedAuth, err := encodeAuthToBase64(authConfig)
+	authConfig := cli.ResolveAuthConfig(ctx, repoInfo.Index)
+	encodedAuth, err := EncodeAuthToBase64(authConfig)
 	if err != nil {
 		return err
 	}

+ 135 - 0
api/client/image/search.go

@@ -0,0 +1,135 @@
+package image
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+	"text/tabwriter"
+
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"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"
+	"github.com/spf13/cobra"
+)
+
+type searchOptions struct {
+	term    string
+	noTrunc bool
+	limit   int
+	filter  []string
+
+	// Deprecated
+	stars     uint
+	automated bool
+}
+
+// NewSearchCommand create a new `docker search` command
+func NewSearchCommand(dockerCli *client.DockerCli) *cobra.Command {
+	var opts searchOptions
+
+	cmd := &cobra.Command{
+		Use:   "search [OPTIONS] TERM",
+		Short: "Search the Docker Hub for images",
+		Args:  cli.ExactArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			opts.term = args[0]
+			return runSearch(dockerCli, opts)
+		},
+	}
+
+	flags := cmd.Flags()
+
+	flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output")
+	flags.StringSliceVarP(&opts.filter, "filter", "f", []string{}, "Filter output based on conditions provided")
+	flags.IntVar(&opts.limit, "limit", registry.DefaultSearchLimit, "Max number of search results")
+
+	flags.BoolVar(&opts.automated, "automated", false, "Only show automated builds")
+	flags.UintVarP(&opts.stars, "stars", "s", 0, "Only displays with at least x stars")
+
+	flags.MarkDeprecated("automated", "Use --filter=automated=true instead")
+	flags.MarkDeprecated("stars", "Use --filter=stars=3 instead")
+
+	return cmd
+}
+
+func runSearch(dockerCli *client.DockerCli, opts searchOptions) error {
+	indexInfo, err := registry.ParseSearchIndexInfo(opts.term)
+	if err != nil {
+		return err
+	}
+
+	ctx := context.Background()
+
+	authConfig := dockerCli.ResolveAuthConfig(ctx, indexInfo)
+	requestPrivilege := dockerCli.RegistryAuthenticationPrivilegedFunc(indexInfo, "search")
+
+	encodedAuth, err := client.EncodeAuthToBase64(authConfig)
+	if err != nil {
+		return err
+	}
+
+	searchFilters := filters.NewArgs()
+	for _, f := range opts.filter {
+		var err error
+		searchFilters, err = filters.ParseFlag(f, searchFilters)
+		if err != nil {
+			return err
+		}
+	}
+
+	options := types.ImageSearchOptions{
+		RegistryAuth:  encodedAuth,
+		PrivilegeFunc: requestPrivilege,
+		Filters:       searchFilters,
+		Limit:         opts.limit,
+	}
+
+	clnt := dockerCli.Client()
+
+	unorderedResults, err := clnt.ImageSearch(ctx, opts.term, options)
+	if err != nil {
+		return err
+	}
+
+	results := searchResultsByStars(unorderedResults)
+	sort.Sort(results)
+
+	w := tabwriter.NewWriter(dockerCli.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 (opts.automated && !res.IsAutomated) || (int(opts.stars) > res.StarCount) {
+			continue
+		}
+		desc := strings.Replace(res.Description, "\n", " ", -1)
+		desc = strings.Replace(desc, "\r", " ", -1)
+		if !opts.noTrunc && len(desc) > 45 {
+			desc = stringutils.Truncate(desc, 42) + "..."
+		}
+		fmt.Fprintf(w, "%s\t%s\t%d\t", res.Name, desc, res.StarCount)
+		if res.IsOfficial {
+			fmt.Fprint(w, "[OK]")
+
+		}
+		fmt.Fprint(w, "\t")
+		if res.IsAutomated {
+			fmt.Fprint(w, "[OK]")
+		}
+		fmt.Fprint(w, "\n")
+	}
+	w.Flush()
+	return nil
+}
+
+// SearchResultsByStars sorts search results in descending order by number of stars.
+type searchResultsByStars []registrytypes.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 }

+ 3 - 3
api/client/pull.go

@@ -57,8 +57,8 @@ func (cli *DockerCli) CmdPull(args ...string) error {
 
 	ctx := context.Background()
 
-	authConfig := cli.resolveAuthConfig(ctx, repoInfo.Index)
-	requestPrivilege := cli.registryAuthenticationPrivilegedFunc(repoInfo.Index, "pull")
+	authConfig := cli.ResolveAuthConfig(ctx, repoInfo.Index)
+	requestPrivilege := cli.RegistryAuthenticationPrivilegedFunc(repoInfo.Index, "pull")
 
 	if isTrusted() && !registryRef.HasDigest() {
 		// Check if tag is digest
@@ -70,7 +70,7 @@ func (cli *DockerCli) CmdPull(args ...string) error {
 
 func (cli *DockerCli) imagePullPrivileged(ctx context.Context, authConfig types.AuthConfig, ref string, requestPrivilege types.RequestPrivilegeFunc, all bool) error {
 
-	encodedAuth, err := encodeAuthToBase64(authConfig)
+	encodedAuth, err := EncodeAuthToBase64(authConfig)
 	if err != nil {
 		return err
 	}

+ 3 - 3
api/client/push.go

@@ -37,8 +37,8 @@ func (cli *DockerCli) CmdPush(args ...string) error {
 	ctx := context.Background()
 
 	// Resolve the Auth config relevant for this server
-	authConfig := cli.resolveAuthConfig(ctx, repoInfo.Index)
-	requestPrivilege := cli.registryAuthenticationPrivilegedFunc(repoInfo.Index, "push")
+	authConfig := cli.ResolveAuthConfig(ctx, repoInfo.Index)
+	requestPrivilege := cli.RegistryAuthenticationPrivilegedFunc(repoInfo.Index, "push")
 
 	if isTrusted() {
 		return cli.trustedPush(ctx, repoInfo, ref, authConfig, requestPrivilege)
@@ -55,7 +55,7 @@ func (cli *DockerCli) CmdPush(args ...string) error {
 }
 
 func (cli *DockerCli) imagePushPrivileged(ctx context.Context, authConfig types.AuthConfig, ref string, requestPrivilege types.RequestPrivilegeFunc) (io.ReadCloser, error) {
-	encodedAuth, err := encodeAuthToBase64(authConfig)
+	encodedAuth, err := EncodeAuthToBase64(authConfig)
 	if err != nil {
 		return nil, err
 	}

+ 0 - 119
api/client/search.go

@@ -1,119 +0,0 @@
-package client
-
-import (
-	"fmt"
-	"net/url"
-	"sort"
-	"strings"
-	"text/tabwriter"
-
-	"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"
-)
-
-// CmdSearch searches the Docker Hub for images.
-//
-// 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")
-	cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
-	flLimit := cmd.Int([]string{"-limit"}, registry.DefaultSearchLimit, "Max number of search results")
-
-	// 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)
-
-	indexInfo, err := registry.ParseSearchIndexInfo(name)
-	if err != nil {
-		return err
-	}
-
-	ctx := context.Background()
-
-	authConfig := cli.resolveAuthConfig(ctx, indexInfo)
-	requestPrivilege := cli.registryAuthenticationPrivilegedFunc(indexInfo, "search")
-
-	encodedAuth, err := encodeAuthToBase64(authConfig)
-	if err != nil {
-		return err
-	}
-
-	options := types.ImageSearchOptions{
-		RegistryAuth:  encodedAuth,
-		PrivilegeFunc: requestPrivilege,
-		Filters:       filterArgs,
-		Limit:         *flLimit,
-	}
-
-	unorderedResults, err := cli.client.ImageSearch(ctx, name, options)
-	if err != nil {
-		return err
-	}
-
-	results := searchResultsByStars(unorderedResults)
-	sort.Sort(results)
-
-	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
-		}
-		desc := strings.Replace(res.Description, "\n", " ", -1)
-		desc = strings.Replace(desc, "\r", " ", -1)
-		if !*noTrunc && len(desc) > 45 {
-			desc = stringutils.Truncate(desc, 42) + "..."
-		}
-		fmt.Fprintf(w, "%s\t%s\t%d\t", res.Name, desc, res.StarCount)
-		if res.IsOfficial {
-			fmt.Fprint(w, "[OK]")
-
-		}
-		fmt.Fprint(w, "\t")
-		if res.IsAutomated {
-			fmt.Fprint(w, "[OK]")
-		}
-		fmt.Fprint(w, "\n")
-	}
-	w.Flush()
-	return nil
-}
-
-// SearchResultsByStars sorts search results in descending order by number of stars.
-type searchResultsByStars []registrytypes.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 }

+ 1 - 1
api/client/trust.go

@@ -236,7 +236,7 @@ func (cli *DockerCli) trustedReference(ctx context.Context, ref reference.NamedT
 	}
 
 	// Resolve the Auth config relevant for this server
-	authConfig := cli.resolveAuthConfig(ctx, repoInfo.Index)
+	authConfig := cli.ResolveAuthConfig(ctx, repoInfo.Index)
 
 	notaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig, "pull")
 	if err != nil {

+ 8 - 6
api/client/utils.go

@@ -37,8 +37,8 @@ func (cli *DockerCli) electAuthServer(ctx context.Context) string {
 	return serverAddress
 }
 
-// encodeAuthToBase64 serializes the auth configuration as JSON base64 payload
-func encodeAuthToBase64(authConfig types.AuthConfig) (string, error) {
+// EncodeAuthToBase64 serializes the auth configuration as JSON base64 payload
+func EncodeAuthToBase64(authConfig types.AuthConfig) (string, error) {
 	buf, err := json.Marshal(authConfig)
 	if err != nil {
 		return "", err
@@ -46,7 +46,9 @@ func encodeAuthToBase64(authConfig types.AuthConfig) (string, error) {
 	return base64.URLEncoding.EncodeToString(buf), nil
 }
 
-func (cli *DockerCli) registryAuthenticationPrivilegedFunc(index *registrytypes.IndexInfo, cmdName string) types.RequestPrivilegeFunc {
+// RegistryAuthenticationPrivilegedFunc return a RequestPrivilegeFunc from the specified registry index info
+// for the given command.
+func (cli *DockerCli) RegistryAuthenticationPrivilegedFunc(index *registrytypes.IndexInfo, cmdName string) types.RequestPrivilegeFunc {
 	return func() (string, error) {
 		fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName)
 		indexServer := registry.GetAuthConfigKey(index)
@@ -54,7 +56,7 @@ func (cli *DockerCli) registryAuthenticationPrivilegedFunc(index *registrytypes.
 		if err != nil {
 			return "", err
 		}
-		return encodeAuthToBase64(authConfig)
+		return EncodeAuthToBase64(authConfig)
 	}
 }
 
@@ -182,10 +184,10 @@ func copyToFile(outfile string, r io.Reader) error {
 	return nil
 }
 
-// resolveAuthConfig is like registry.ResolveAuthConfig, but if using the
+// ResolveAuthConfig is like registry.ResolveAuthConfig, but if using the
 // default index, it uses the default index name for the daemon's platform,
 // not the client's platform.
-func (cli *DockerCli) resolveAuthConfig(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig {
+func (cli *DockerCli) ResolveAuthConfig(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig {
 	configKey := index.Name
 	if index.Official {
 		configKey = cli.electAuthServer(ctx)

+ 2 - 0
cli/cobraadaptor/adaptor.go

@@ -4,6 +4,7 @@ import (
 	"fmt"
 
 	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/api/client/image"
 	"github.com/docker/docker/api/client/volume"
 	"github.com/docker/docker/cli"
 	cliflags "github.com/docker/docker/cli/flags"
@@ -34,6 +35,7 @@ func NewCobraAdaptor(clientFlags *cliflags.ClientFlags) CobraAdaptor {
 	rootCmd.SetOutput(stdout)
 	rootCmd.AddCommand(
 		volume.NewVolumeCommand(dockerCli),
+		image.NewSearchCommand(dockerCli),
 	)
 
 	rootCmd.PersistentFlags().BoolP("help", "h", false, "Print usage")

+ 16 - 0
cli/required.go

@@ -40,3 +40,19 @@ func RequiresMinArgs(min int) cobra.PositionalArgs {
 		)
 	}
 }
+
+// ExactArgs returns an error if there is not the exact number of args
+func ExactArgs(number int) cobra.PositionalArgs {
+	return func(cmd *cobra.Command, args []string) error {
+		if len(args) == number {
+			return nil
+		}
+		return fmt.Errorf(
+			"\"%s\" requires exactly %d argument(s).\n\nUsage:  %s\n\n%s",
+			cmd.CommandPath(),
+			number,
+			cmd.UseLine(),
+			cmd.Short,
+		)
+	}
+}

+ 0 - 1
cli/usage.go

@@ -39,7 +39,6 @@ var DockerCommandUsage = []Command{
 	{"rmi", "Remove one or more images"},
 	{"run", "Run a command in a new container"},
 	{"save", "Save one or more images to a tar archive"},
-	{"search", "Search the Docker Hub for images"},
 	{"start", "Start one or more stopped containers"},
 	{"stats", "Display a live stream of container(s) resource usage statistics"},
 	{"stop", "Stop a running container"},

+ 2 - 2
integration-cli/docker_cli_search_test.go

@@ -36,12 +36,12 @@ func (s *DockerSuite) TestSearchStarsOptionWithWrongParameter(c *check.C) {
 	// -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"))
+	c.Assert(out, checker.Contains, "invalid syntax", 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"))
+	c.Assert(out, checker.Contains, "invalid syntax", check.Commentf("couldn't find the invalid value warning"))
 }
 
 func (s *DockerSuite) TestSearchCmdOptions(c *check.C) {