128 lines
4.1 KiB
Go
128 lines
4.1 KiB
Go
package registry
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/docker/cli/cli"
|
|
"github.com/docker/cli/cli/command"
|
|
"github.com/docker/cli/cli/command/formatter"
|
|
"github.com/docker/cli/internal/commands"
|
|
"github.com/docker/cli/opts"
|
|
"github.com/moby/moby/api/pkg/authconfig"
|
|
registrytypes "github.com/moby/moby/api/types/registry"
|
|
"github.com/moby/moby/client"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
func init() {
|
|
commands.Register(newSearchCommand)
|
|
}
|
|
|
|
type searchOptions struct {
|
|
format string
|
|
term string
|
|
noTrunc bool
|
|
limit int
|
|
filter opts.FilterOpt
|
|
}
|
|
|
|
// newSearchCommand creates a new `docker search` command
|
|
func newSearchCommand(dockerCLI command.Cli) *cobra.Command {
|
|
options := searchOptions{filter: opts.NewFilterOpt()}
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "search [OPTIONS] TERM",
|
|
Short: "Search Docker Hub for images",
|
|
Args: cli.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
options.term = args[0]
|
|
return runSearch(cmd.Context(), dockerCLI, options)
|
|
},
|
|
Annotations: map[string]string{
|
|
"category-top": "10",
|
|
},
|
|
DisableFlagsInUseLine: true,
|
|
}
|
|
|
|
flags := cmd.Flags()
|
|
|
|
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Don't truncate output")
|
|
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
|
|
flags.IntVar(&options.limit, "limit", 0, "Max number of search results")
|
|
flags.StringVar(&options.format, "format", "", "Pretty-print search using a Go template")
|
|
|
|
return cmd
|
|
}
|
|
|
|
func runSearch(ctx context.Context, dockerCli command.Cli, options searchOptions) error {
|
|
if _, ok := options.filter.Value()["is-automated"]; ok {
|
|
_, _ = fmt.Fprintln(dockerCli.Err(), `WARNING: the "is-automated" filter is deprecated, and searching for "is-automated=true" will not yield any results in future.`)
|
|
}
|
|
encodedAuth, err := getAuth(dockerCli, options.term)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
results, err := dockerCli.Client().ImageSearch(ctx, options.term, client.ImageSearchOptions{
|
|
RegistryAuth: encodedAuth,
|
|
PrivilegeFunc: nil,
|
|
Filters: options.filter.Value(),
|
|
Limit: options.limit,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
searchCtx := formatter.Context{
|
|
Output: dockerCli.Out(),
|
|
Format: newFormat(options.format),
|
|
Trunc: !options.noTrunc,
|
|
}
|
|
return formatWrite(searchCtx, results)
|
|
}
|
|
|
|
// authConfigKey is the key used to store credentials for Docker Hub. It is
|
|
// a copy of [registry.IndexServer].
|
|
//
|
|
// [registry.IndexServer]: https://pkg.go.dev/github.com/docker/docker/registry#IndexServer
|
|
const authConfigKey = "https://index.docker.io/v1/"
|
|
|
|
// getAuth will use fetch auth based on the given search-term. If the search
|
|
// does not contain a hostname for the registry, it assumes Docker Hub is used,
|
|
// and resolves authentication for Docker Hub, otherwise it resolves authentication
|
|
// for the given registry.
|
|
func getAuth(dockerCLI command.Cli, reposName string) (encodedAuth string, err error) {
|
|
authCfgKey := splitReposSearchTerm(reposName)
|
|
if authCfgKey == "docker.io" || authCfgKey == "index.docker.io" {
|
|
authCfgKey = authConfigKey
|
|
}
|
|
|
|
// Ignoring errors here, which was the existing behavior (likely
|
|
// "no credentials found"). We'll get an error when search failed,
|
|
// so fine to ignore in most situations.
|
|
authConfig, _ := dockerCLI.ConfigFile().GetAuthConfig(authCfgKey)
|
|
return authconfig.Encode(registrytypes.AuthConfig{
|
|
Username: authConfig.Username,
|
|
Password: authConfig.Password,
|
|
ServerAddress: authConfig.ServerAddress,
|
|
|
|
// TODO(thaJeztah): Are these expected to be included?
|
|
Auth: authConfig.Auth,
|
|
IdentityToken: authConfig.IdentityToken,
|
|
RegistryToken: authConfig.RegistryToken,
|
|
})
|
|
}
|
|
|
|
// splitReposSearchTerm breaks a search term into an index name and remote name
|
|
func splitReposSearchTerm(reposName string) string {
|
|
nameParts := strings.SplitN(reposName, "/", 2)
|
|
if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") && !strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") {
|
|
// This is a Docker Hub repository (ex: samalba/hipache or ubuntu),
|
|
// use the default Docker Hub registry (docker.io)
|
|
return "docker.io"
|
|
}
|
|
return nameParts[0]
|
|
}
|