mirror of https://github.com/docker/docs.git
				
				
				
			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>
This commit is contained in:
		
							parent
							
								
									147c8b7495
								
							
						
					
					
						commit
						a11ef10631
					
				|  | @ -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, | ||||
|  |  | |||
|  | @ -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 | ||||
| 	} | ||||
|  |  | |||
|  | @ -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 } | ||||
|  | @ -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 | ||||
| 	} | ||||
|  |  | |||
|  | @ -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 | ||||
| 	} | ||||
|  |  | |||
|  | @ -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 } | ||||
|  | @ -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 { | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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") | ||||
|  |  | |||
|  | @ -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, | ||||
| 		) | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -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"}, | ||||
|  |  | |||
|  | @ -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) { | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue