diff --git a/api/client/commands.go b/api/client/commands.go index b843106a72..bbaed21587 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -8,8 +8,6 @@ func (cli *DockerCli) Command(name string) func(...string) error { "exec": cli.CmdExec, "info": cli.CmdInfo, "inspect": cli.CmdInspect, - "login": cli.CmdLogin, - "logout": cli.CmdLogout, "ps": cli.CmdPs, "pull": cli.CmdPull, "push": cli.CmdPush, diff --git a/api/client/credentials.go b/api/client/credentials.go new file mode 100644 index 0000000000..9f5e80c7a9 --- /dev/null +++ b/api/client/credentials.go @@ -0,0 +1,44 @@ +package client + +import ( + "github.com/docker/docker/cliconfig/configfile" + "github.com/docker/docker/cliconfig/credentials" + "github.com/docker/engine-api/types" +) + +// GetCredentials loads the user credentials from a credentials store. +// The store is determined by the config file settings. +func GetCredentials(c *configfile.ConfigFile, serverAddress string) (types.AuthConfig, error) { + s := LoadCredentialsStore(c) + return s.Get(serverAddress) +} + +// GetAllCredentials loads all credentials from a credentials store. +// The store is determined by the config file settings. +func GetAllCredentials(c *configfile.ConfigFile) (map[string]types.AuthConfig, error) { + s := LoadCredentialsStore(c) + return s.GetAll() +} + +// StoreCredentials saves the user credentials in a credentials store. +// The store is determined by the config file settings. +func StoreCredentials(c *configfile.ConfigFile, auth types.AuthConfig) error { + s := LoadCredentialsStore(c) + return s.Store(auth) +} + +// EraseCredentials removes the user credentials from a credentials store. +// The store is determined by the config file settings. +func EraseCredentials(c *configfile.ConfigFile, serverAddress string) error { + s := LoadCredentialsStore(c) + return s.Erase(serverAddress) +} + +// LoadCredentialsStore initializes a new credentials store based +// in the settings provided in the configuration file. +func LoadCredentialsStore(c *configfile.ConfigFile) credentials.Store { + if c.CredentialsStore != "" { + return credentials.NewNativeStore(c) + } + return credentials.NewFileStore(c) +} diff --git a/api/client/image/search.go b/api/client/image/search.go index dede522cca..d42b8aaf6d 100644 --- a/api/client/image/search.go +++ b/api/client/image/search.go @@ -52,8 +52,8 @@ func NewSearchCommand(dockerCli *client.DockerCli) *cobra.Command { 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") + flags.MarkDeprecated("automated", "use --filter=automated=true instead") + flags.MarkDeprecated("stars", "use --filter=stars=3 instead") return cmd } diff --git a/api/client/login.go b/api/client/login.go deleted file mode 100644 index 265466d300..0000000000 --- a/api/client/login.go +++ /dev/null @@ -1,184 +0,0 @@ -package client - -import ( - "bufio" - "fmt" - "io" - "os" - "runtime" - "strings" - - "golang.org/x/net/context" - - Cli "github.com/docker/docker/cli" - "github.com/docker/docker/cliconfig/configfile" - "github.com/docker/docker/cliconfig/credentials" - flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/pkg/term" - "github.com/docker/engine-api/types" -) - -// CmdLogin logs in a user to a Docker registry service. -// -// If no server is specified, the user will be logged into or registered to the registry's index server. -// -// Usage: docker login SERVER -func (cli *DockerCli) CmdLogin(args ...string) error { - cmd := Cli.Subcmd("login", []string{"[SERVER]"}, Cli.DockerCommands["login"].Description+".\nIf no server is specified, the default is defined by the daemon.", true) - cmd.Require(flag.Max, 1) - - flUser := cmd.String([]string{"u", "-username"}, "", "Username") - flPassword := cmd.String([]string{"p", "-password"}, "", "Password") - - // Deprecated in 1.11: Should be removed in docker 1.13 - cmd.String([]string{"#e", "#-email"}, "", "Email") - - cmd.ParseFlags(args, true) - - // On Windows, force the use of the regular OS stdin stream. Fixes #14336/#14210 - if runtime.GOOS == "windows" { - cli.in = os.Stdin - } - - ctx := context.Background() - var serverAddress string - var isDefaultRegistry bool - if len(cmd.Args()) > 0 { - serverAddress = cmd.Arg(0) - } else { - serverAddress = cli.electAuthServer(ctx) - isDefaultRegistry = true - } - authConfig, err := cli.configureAuth(*flUser, *flPassword, serverAddress, isDefaultRegistry) - if err != nil { - return err - } - response, err := cli.client.RegistryLogin(ctx, authConfig) - if err != nil { - return err - } - if response.IdentityToken != "" { - authConfig.Password = "" - authConfig.IdentityToken = response.IdentityToken - } - if err := storeCredentials(cli.configFile, authConfig); err != nil { - return fmt.Errorf("Error saving credentials: %v", err) - } - - if response.Status != "" { - fmt.Fprintln(cli.out, response.Status) - } - return nil -} - -func (cli *DockerCli) promptWithDefault(prompt string, configDefault string) { - if configDefault == "" { - fmt.Fprintf(cli.out, "%s: ", prompt) - } else { - fmt.Fprintf(cli.out, "%s (%s): ", prompt, configDefault) - } -} - -func (cli *DockerCli) configureAuth(flUser, flPassword, serverAddress string, isDefaultRegistry bool) (types.AuthConfig, error) { - authconfig, err := getCredentials(cli.configFile, serverAddress) - if err != nil { - return authconfig, err - } - - // Some links documenting this: - // - https://code.google.com/archive/p/mintty/issues/56 - // - https://github.com/docker/docker/issues/15272 - // - https://mintty.github.io/ (compatibility) - // Linux will hit this if you attempt `cat | docker login`, and Windows - // will hit this if you attempt docker login from mintty where stdin - // is a pipe, not a character based console. - if flPassword == "" && !cli.isTerminalIn { - return authconfig, fmt.Errorf("Error: Cannot perform an interactive logon from a non TTY device") - } - - authconfig.Username = strings.TrimSpace(authconfig.Username) - - if flUser = strings.TrimSpace(flUser); flUser == "" { - if isDefaultRegistry { - // if this is a defauly registry (docker hub), then display the following message. - fmt.Fprintln(cli.out, "Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.") - } - cli.promptWithDefault("Username", authconfig.Username) - flUser = readInput(cli.in, cli.out) - flUser = strings.TrimSpace(flUser) - if flUser == "" { - flUser = authconfig.Username - } - } - if flUser == "" { - return authconfig, fmt.Errorf("Error: Non-null Username Required") - } - if flPassword == "" { - oldState, err := term.SaveState(cli.inFd) - if err != nil { - return authconfig, err - } - fmt.Fprintf(cli.out, "Password: ") - term.DisableEcho(cli.inFd, oldState) - - flPassword = readInput(cli.in, cli.out) - fmt.Fprint(cli.out, "\n") - - term.RestoreTerminal(cli.inFd, oldState) - if flPassword == "" { - return authconfig, fmt.Errorf("Error: Password Required") - } - } - - authconfig.Username = flUser - authconfig.Password = flPassword - authconfig.ServerAddress = serverAddress - authconfig.IdentityToken = "" - - return authconfig, nil -} - -func readInput(in io.Reader, out io.Writer) string { - reader := bufio.NewReader(in) - line, _, err := reader.ReadLine() - if err != nil { - fmt.Fprintln(out, err.Error()) - os.Exit(1) - } - return string(line) -} - -// getCredentials loads the user credentials from a credentials store. -// The store is determined by the config file settings. -func getCredentials(c *configfile.ConfigFile, serverAddress string) (types.AuthConfig, error) { - s := loadCredentialsStore(c) - return s.Get(serverAddress) -} - -func getAllCredentials(c *configfile.ConfigFile) (map[string]types.AuthConfig, error) { - s := loadCredentialsStore(c) - return s.GetAll() -} - -// storeCredentials saves the user credentials in a credentials store. -// The store is determined by the config file settings. -func storeCredentials(c *configfile.ConfigFile, auth types.AuthConfig) error { - s := loadCredentialsStore(c) - return s.Store(auth) -} - -// eraseCredentials removes the user credentials from a credentials store. -// The store is determined by the config file settings. -func eraseCredentials(c *configfile.ConfigFile, serverAddress string) error { - s := loadCredentialsStore(c) - return s.Erase(serverAddress) -} - -// loadCredentialsStore initializes a new credentials store based -// in the settings provided in the configuration file. -func loadCredentialsStore(c *configfile.ConfigFile) credentials.Store { - if c.CredentialsStore != "" { - return credentials.NewNativeStore(c) - } - return credentials.NewFileStore(c) -} diff --git a/api/client/logout.go b/api/client/logout.go deleted file mode 100644 index ce3043bd02..0000000000 --- a/api/client/logout.go +++ /dev/null @@ -1,43 +0,0 @@ -package client - -import ( - "fmt" - - "golang.org/x/net/context" - - Cli "github.com/docker/docker/cli" - flag "github.com/docker/docker/pkg/mflag" -) - -// CmdLogout logs a user out from a Docker registry. -// -// If no server is specified, the user will be logged out from the registry's index server. -// -// Usage: docker logout [SERVER] -func (cli *DockerCli) CmdLogout(args ...string) error { - cmd := Cli.Subcmd("logout", []string{"[SERVER]"}, Cli.DockerCommands["logout"].Description+".\nIf no server is specified, the default is defined by the daemon.", true) - cmd.Require(flag.Max, 1) - - cmd.ParseFlags(args, true) - - var serverAddress string - if len(cmd.Args()) > 0 { - serverAddress = cmd.Arg(0) - } else { - serverAddress = cli.electAuthServer(context.Background()) - } - - // check if we're logged in based on the records in the config file - // which means it couldn't have user/pass cause they may be in the creds store - if _, ok := cli.configFile.AuthConfigs[serverAddress]; !ok { - fmt.Fprintf(cli.out, "Not logged in to %s\n", serverAddress) - return nil - } - - fmt.Fprintf(cli.out, "Remove login credentials for %s\n", serverAddress) - if err := eraseCredentials(cli.configFile, serverAddress); err != nil { - fmt.Fprintf(cli.out, "WARNING: could not erase credentials: %v\n", err) - } - - return nil -} diff --git a/api/client/registry.go b/api/client/registry.go new file mode 100644 index 0000000000..339a67bfe1 --- /dev/null +++ b/api/client/registry.go @@ -0,0 +1,159 @@ +package client + +import ( + "bufio" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "os" + "runtime" + "strings" + + "golang.org/x/net/context" + + "github.com/docker/docker/pkg/term" + "github.com/docker/docker/registry" + "github.com/docker/engine-api/types" + registrytypes "github.com/docker/engine-api/types/registry" +) + +// ElectAuthServer returns the default registry to use (by asking the daemon) +func (cli *DockerCli) ElectAuthServer(ctx context.Context) string { + // The daemon `/info` endpoint informs us of the default registry being + // used. This is essential in cross-platforms environment, where for + // example a Linux client might be interacting with a Windows daemon, hence + // the default registry URL might be Windows specific. + serverAddress := registry.IndexServer + if info, err := cli.client.Info(ctx); err != nil { + fmt.Fprintf(cli.out, "Warning: failed to get default registry endpoint from daemon (%v). Using system default: %s\n", err, serverAddress) + } else { + serverAddress = info.IndexServerAddress + } + return serverAddress +} + +// 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 + } + return base64.URLEncoding.EncodeToString(buf), nil +} + +// 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) + authConfig, err := cli.ConfigureAuth("", "", indexServer, false) + if err != nil { + return "", err + } + return EncodeAuthToBase64(authConfig) + } +} + +func (cli *DockerCli) promptWithDefault(prompt string, configDefault string) { + if configDefault == "" { + fmt.Fprintf(cli.out, "%s: ", prompt) + } else { + fmt.Fprintf(cli.out, "%s (%s): ", prompt, configDefault) + } +} + +// 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 { + configKey := index.Name + if index.Official { + configKey = cli.ElectAuthServer(ctx) + } + + a, _ := GetCredentials(cli.configFile, configKey) + return a +} + +// RetrieveAuthConfigs return all credentials. +func (cli *DockerCli) RetrieveAuthConfigs() map[string]types.AuthConfig { + acs, _ := GetAllCredentials(cli.configFile) + return acs +} + +// ConfigureAuth returns an AuthConfig from the specified user, password and server. +func (cli *DockerCli) ConfigureAuth(flUser, flPassword, serverAddress string, isDefaultRegistry bool) (types.AuthConfig, error) { + // On Windows, force the use of the regular OS stdin stream. Fixes #14336/#14210 + if runtime.GOOS == "windows" { + cli.in = os.Stdin + } + + authconfig, err := GetCredentials(cli.configFile, serverAddress) + if err != nil { + return authconfig, err + } + + // Some links documenting this: + // - https://code.google.com/archive/p/mintty/issues/56 + // - https://github.com/docker/docker/issues/15272 + // - https://mintty.github.io/ (compatibility) + // Linux will hit this if you attempt `cat | docker login`, and Windows + // will hit this if you attempt docker login from mintty where stdin + // is a pipe, not a character based console. + if flPassword == "" && !cli.isTerminalIn { + return authconfig, fmt.Errorf("Error: Cannot perform an interactive logon from a non TTY device") + } + + authconfig.Username = strings.TrimSpace(authconfig.Username) + + if flUser = strings.TrimSpace(flUser); flUser == "" { + if isDefaultRegistry { + // if this is a defauly registry (docker hub), then display the following message. + fmt.Fprintln(cli.out, "Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.") + } + cli.promptWithDefault("Username", authconfig.Username) + flUser = readInput(cli.in, cli.out) + flUser = strings.TrimSpace(flUser) + if flUser == "" { + flUser = authconfig.Username + } + } + if flUser == "" { + return authconfig, fmt.Errorf("Error: Non-null Username Required") + } + if flPassword == "" { + oldState, err := term.SaveState(cli.inFd) + if err != nil { + return authconfig, err + } + fmt.Fprintf(cli.out, "Password: ") + term.DisableEcho(cli.inFd, oldState) + + flPassword = readInput(cli.in, cli.out) + fmt.Fprint(cli.out, "\n") + + term.RestoreTerminal(cli.inFd, oldState) + if flPassword == "" { + return authconfig, fmt.Errorf("Error: Password Required") + } + } + + authconfig.Username = flUser + authconfig.Password = flPassword + authconfig.ServerAddress = serverAddress + authconfig.IdentityToken = "" + + return authconfig, nil +} + +func readInput(in io.Reader, out io.Writer) string { + reader := bufio.NewReader(in) + line, _, err := reader.ReadLine() + if err != nil { + fmt.Fprintln(out, err.Error()) + os.Exit(1) + } + return string(line) +} diff --git a/api/client/registry/login.go b/api/client/registry/login.go new file mode 100644 index 0000000000..452ac71513 --- /dev/null +++ b/api/client/registry/login.go @@ -0,0 +1,81 @@ +package registry + +import ( + "fmt" + + "golang.org/x/net/context" + + "github.com/docker/docker/api/client" + "github.com/docker/docker/cli" + "github.com/spf13/cobra" +) + +type loginOptions struct { + serverAddress string + user string + password string + email string +} + +// NewLoginCommand creates a new `docker login` command +func NewLoginCommand(dockerCli *client.DockerCli) *cobra.Command { + var opts loginOptions + + cmd := &cobra.Command{ + Use: "login [OPTIONS] [SERVER]", + Short: "Log in to a Docker registry.", + Long: "Log in to a Docker registry.\nIf no server is specified, the default is defined by the daemon.", + Args: cli.RequiresMaxArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) > 0 { + opts.serverAddress = args[0] + } + return runLogin(dockerCli, opts) + }, + } + + flags := cmd.Flags() + + flags.StringVarP(&opts.user, "username", "u", "", "Username") + flags.StringVarP(&opts.password, "password", "p", "", "Password") + + // Deprecated in 1.11: Should be removed in docker 1.13 + flags.StringVarP(&opts.email, "email", "e", "", "Email") + flags.MarkDeprecated("email", "will be removed in 1.13.") + + return cmd +} + +func runLogin(dockerCli *client.DockerCli, opts loginOptions) error { + ctx := context.Background() + clnt := dockerCli.Client() + + var serverAddress string + var isDefaultRegistry bool + if opts.serverAddress != "" { + serverAddress = opts.serverAddress + } else { + serverAddress = dockerCli.ElectAuthServer(ctx) + isDefaultRegistry = true + } + authConfig, err := dockerCli.ConfigureAuth(opts.user, opts.password, serverAddress, isDefaultRegistry) + if err != nil { + return err + } + response, err := clnt.RegistryLogin(ctx, authConfig) + if err != nil { + return err + } + if response.IdentityToken != "" { + authConfig.Password = "" + authConfig.IdentityToken = response.IdentityToken + } + if err := client.StoreCredentials(dockerCli.ConfigFile(), authConfig); err != nil { + return fmt.Errorf("Error saving credentials: %v", err) + } + + if response.Status != "" { + fmt.Fprintln(dockerCli.Out(), response.Status) + } + return nil +} diff --git a/api/client/registry/logout.go b/api/client/registry/logout.go new file mode 100644 index 0000000000..b7497a4743 --- /dev/null +++ b/api/client/registry/logout.go @@ -0,0 +1,52 @@ +package registry + +import ( + "fmt" + + "golang.org/x/net/context" + + "github.com/docker/docker/api/client" + "github.com/docker/docker/cli" + "github.com/spf13/cobra" +) + +// NewLogoutCommand creates a new `docker login` command +func NewLogoutCommand(dockerCli *client.DockerCli) *cobra.Command { + cmd := &cobra.Command{ + Use: "logout [SERVER]", + Short: "Log out from a Docker registry.", + Long: "Log out from a Docker registry.\nIf no server is specified, the default is defined by the daemon.", + Args: cli.RequiresMaxArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + var serverAddress string + if len(args) > 0 { + serverAddress = args[0] + } + return runLogout(dockerCli, serverAddress) + }, + } + + return cmd +} + +func runLogout(dockerCli *client.DockerCli, serverAddress string) error { + ctx := context.Background() + + if serverAddress == "" { + serverAddress = dockerCli.ElectAuthServer(ctx) + } + + // check if we're logged in based on the records in the config file + // which means it couldn't have user/pass cause they may be in the creds store + if _, ok := dockerCli.ConfigFile().AuthConfigs[serverAddress]; !ok { + fmt.Fprintf(dockerCli.Out(), "Not logged in to %s\n", serverAddress) + return nil + } + + fmt.Fprintf(dockerCli.Out(), "Remove login credentials for %s\n", serverAddress) + if err := client.EraseCredentials(dockerCli.ConfigFile(), serverAddress); err != nil { + fmt.Fprintf(dockerCli.Out(), "WARNING: could not erase credentials: %v\n", err) + } + + return nil +} diff --git a/api/client/utils.go b/api/client/utils.go index 3a01a3cae9..850b598c73 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -1,8 +1,6 @@ package client import ( - "encoding/base64" - "encoding/json" "fmt" "io" "io/ioutil" @@ -17,49 +15,10 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/term" - "github.com/docker/docker/registry" "github.com/docker/engine-api/client" "github.com/docker/engine-api/types" - registrytypes "github.com/docker/engine-api/types/registry" ) -func (cli *DockerCli) electAuthServer(ctx context.Context) string { - // The daemon `/info` endpoint informs us of the default registry being - // used. This is essential in cross-platforms environment, where for - // example a Linux client might be interacting with a Windows daemon, hence - // the default registry URL might be Windows specific. - serverAddress := registry.IndexServer - if info, err := cli.client.Info(ctx); err != nil { - fmt.Fprintf(cli.out, "Warning: failed to get default registry endpoint from daemon (%v). Using system default: %s\n", err, serverAddress) - } else { - serverAddress = info.IndexServerAddress - } - return serverAddress -} - -// 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 - } - return base64.URLEncoding.EncodeToString(buf), nil -} - -// 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) - authConfig, err := cli.configureAuth("", "", indexServer, false) - if err != nil { - return "", err - } - return EncodeAuthToBase64(authConfig) - } -} - func (cli *DockerCli) resizeTty(ctx context.Context, id string, isExec bool) { height, width := cli.GetTtySize() cli.ResizeTtyTo(ctx, id, height, width, isExec) @@ -189,26 +148,7 @@ func CopyToFile(outfile string, r io.Reader) error { return nil } -// 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 { - configKey := index.Name - if index.Official { - configKey = cli.electAuthServer(ctx) - } - - a, _ := getCredentials(cli.configFile, configKey) - return a -} - -// RetrieveAuthConfigs return all credentials. -func (cli *DockerCli) RetrieveAuthConfigs() map[string]types.AuthConfig { - acs, _ := getAllCredentials(cli.configFile) - return acs -} - -// ForwardAllSignals forwards signals to the container +// ForwardAllSignals forwards signals to the contianer // TODO: this can be unexported again once all container commands are under // api/client/container func (cli *DockerCli) ForwardAllSignals(ctx context.Context, cid string) chan os.Signal { diff --git a/cli/cobraadaptor/adaptor.go b/cli/cobraadaptor/adaptor.go index a9bdd1a170..11edef6f28 100644 --- a/cli/cobraadaptor/adaptor.go +++ b/cli/cobraadaptor/adaptor.go @@ -5,6 +5,7 @@ import ( "github.com/docker/docker/api/client/container" "github.com/docker/docker/api/client/image" "github.com/docker/docker/api/client/network" + "github.com/docker/docker/api/client/registry" "github.com/docker/docker/api/client/system" "github.com/docker/docker/api/client/volume" "github.com/docker/docker/cli" @@ -64,6 +65,8 @@ func NewCobraAdaptor(clientFlags *cliflags.ClientFlags) CobraAdaptor { image.NewTagCommand(dockerCli), network.NewNetworkCommand(dockerCli), system.NewEventsCommand(dockerCli), + registry.NewLoginCommand(dockerCli), + registry.NewLogoutCommand(dockerCli), system.NewVersionCommand(dockerCli), volume.NewVolumeCommand(dockerCli), ) diff --git a/cli/usage.go b/cli/usage.go index 20eae80f74..1e6c3dc202 100644 --- a/cli/usage.go +++ b/cli/usage.go @@ -13,8 +13,6 @@ var DockerCommandUsage = []Command{ {"exec", "Run a command in a running container"}, {"info", "Display system-wide information"}, {"inspect", "Return low-level information on a container or image"}, - {"login", "Log in to a Docker registry"}, - {"logout", "Log out from a Docker registry"}, {"ps", "List containers"}, {"pull", "Pull an image or a repository from a registry"}, {"push", "Push an image or a repository to a registry"},