Also moves some common stuff around :
- `api/client/registry.go` for registry related
method (`ElectAuthServer`, …)
- `api/client/credentials.go` to interact with credentials
Migrate logout command to cobra
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
| ... | ... |
@@ -8,8 +8,6 @@ func (cli *DockerCli) Command(name string) func(...string) error {
|
| 8 | 8 |
"exec": cli.CmdExec, |
| 9 | 9 |
"info": cli.CmdInfo, |
| 10 | 10 |
"inspect": cli.CmdInspect, |
| 11 |
- "login": cli.CmdLogin, |
|
| 12 |
- "logout": cli.CmdLogout, |
|
| 13 | 11 |
"ps": cli.CmdPs, |
| 14 | 12 |
"pull": cli.CmdPull, |
| 15 | 13 |
"push": cli.CmdPush, |
| 16 | 14 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,44 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "github.com/docker/docker/cliconfig/configfile" |
|
| 4 |
+ "github.com/docker/docker/cliconfig/credentials" |
|
| 5 |
+ "github.com/docker/engine-api/types" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+// GetCredentials loads the user credentials from a credentials store. |
|
| 9 |
+// The store is determined by the config file settings. |
|
| 10 |
+func GetCredentials(c *configfile.ConfigFile, serverAddress string) (types.AuthConfig, error) {
|
|
| 11 |
+ s := LoadCredentialsStore(c) |
|
| 12 |
+ return s.Get(serverAddress) |
|
| 13 |
+} |
|
| 14 |
+ |
|
| 15 |
+// GetAllCredentials loads all credentials from a credentials store. |
|
| 16 |
+// The store is determined by the config file settings. |
|
| 17 |
+func GetAllCredentials(c *configfile.ConfigFile) (map[string]types.AuthConfig, error) {
|
|
| 18 |
+ s := LoadCredentialsStore(c) |
|
| 19 |
+ return s.GetAll() |
|
| 20 |
+} |
|
| 21 |
+ |
|
| 22 |
+// StoreCredentials saves the user credentials in a credentials store. |
|
| 23 |
+// The store is determined by the config file settings. |
|
| 24 |
+func StoreCredentials(c *configfile.ConfigFile, auth types.AuthConfig) error {
|
|
| 25 |
+ s := LoadCredentialsStore(c) |
|
| 26 |
+ return s.Store(auth) |
|
| 27 |
+} |
|
| 28 |
+ |
|
| 29 |
+// EraseCredentials removes the user credentials from a credentials store. |
|
| 30 |
+// The store is determined by the config file settings. |
|
| 31 |
+func EraseCredentials(c *configfile.ConfigFile, serverAddress string) error {
|
|
| 32 |
+ s := LoadCredentialsStore(c) |
|
| 33 |
+ return s.Erase(serverAddress) |
|
| 34 |
+} |
|
| 35 |
+ |
|
| 36 |
+// LoadCredentialsStore initializes a new credentials store based |
|
| 37 |
+// in the settings provided in the configuration file. |
|
| 38 |
+func LoadCredentialsStore(c *configfile.ConfigFile) credentials.Store {
|
|
| 39 |
+ if c.CredentialsStore != "" {
|
|
| 40 |
+ return credentials.NewNativeStore(c) |
|
| 41 |
+ } |
|
| 42 |
+ return credentials.NewFileStore(c) |
|
| 43 |
+} |
| ... | ... |
@@ -52,8 +52,8 @@ func NewSearchCommand(dockerCli *client.DockerCli) *cobra.Command {
|
| 52 | 52 |
flags.BoolVar(&opts.automated, "automated", false, "Only show automated builds") |
| 53 | 53 |
flags.UintVarP(&opts.stars, "stars", "s", 0, "Only displays with at least x stars") |
| 54 | 54 |
|
| 55 |
- flags.MarkDeprecated("automated", "Use --filter=automated=true instead")
|
|
| 56 |
- flags.MarkDeprecated("stars", "Use --filter=stars=3 instead")
|
|
| 55 |
+ flags.MarkDeprecated("automated", "use --filter=automated=true instead")
|
|
| 56 |
+ flags.MarkDeprecated("stars", "use --filter=stars=3 instead")
|
|
| 57 | 57 |
|
| 58 | 58 |
return cmd |
| 59 | 59 |
} |
| 60 | 60 |
deleted file mode 100644 |
| ... | ... |
@@ -1,184 +0,0 @@ |
| 1 |
-package client |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "bufio" |
|
| 5 |
- "fmt" |
|
| 6 |
- "io" |
|
| 7 |
- "os" |
|
| 8 |
- "runtime" |
|
| 9 |
- "strings" |
|
| 10 |
- |
|
| 11 |
- "golang.org/x/net/context" |
|
| 12 |
- |
|
| 13 |
- Cli "github.com/docker/docker/cli" |
|
| 14 |
- "github.com/docker/docker/cliconfig/configfile" |
|
| 15 |
- "github.com/docker/docker/cliconfig/credentials" |
|
| 16 |
- flag "github.com/docker/docker/pkg/mflag" |
|
| 17 |
- "github.com/docker/docker/pkg/term" |
|
| 18 |
- "github.com/docker/engine-api/types" |
|
| 19 |
-) |
|
| 20 |
- |
|
| 21 |
-// CmdLogin logs in a user to a Docker registry service. |
|
| 22 |
-// |
|
| 23 |
-// If no server is specified, the user will be logged into or registered to the registry's index server. |
|
| 24 |
-// |
|
| 25 |
-// Usage: docker login SERVER |
|
| 26 |
-func (cli *DockerCli) CmdLogin(args ...string) error {
|
|
| 27 |
- cmd := Cli.Subcmd("login", []string{"[SERVER]"}, Cli.DockerCommands["login"].Description+".\nIf no server is specified, the default is defined by the daemon.", true)
|
|
| 28 |
- cmd.Require(flag.Max, 1) |
|
| 29 |
- |
|
| 30 |
- flUser := cmd.String([]string{"u", "-username"}, "", "Username")
|
|
| 31 |
- flPassword := cmd.String([]string{"p", "-password"}, "", "Password")
|
|
| 32 |
- |
|
| 33 |
- // Deprecated in 1.11: Should be removed in docker 1.13 |
|
| 34 |
- cmd.String([]string{"#e", "#-email"}, "", "Email")
|
|
| 35 |
- |
|
| 36 |
- cmd.ParseFlags(args, true) |
|
| 37 |
- |
|
| 38 |
- // On Windows, force the use of the regular OS stdin stream. Fixes #14336/#14210 |
|
| 39 |
- if runtime.GOOS == "windows" {
|
|
| 40 |
- cli.in = os.Stdin |
|
| 41 |
- } |
|
| 42 |
- |
|
| 43 |
- ctx := context.Background() |
|
| 44 |
- var serverAddress string |
|
| 45 |
- var isDefaultRegistry bool |
|
| 46 |
- if len(cmd.Args()) > 0 {
|
|
| 47 |
- serverAddress = cmd.Arg(0) |
|
| 48 |
- } else {
|
|
| 49 |
- serverAddress = cli.electAuthServer(ctx) |
|
| 50 |
- isDefaultRegistry = true |
|
| 51 |
- } |
|
| 52 |
- authConfig, err := cli.configureAuth(*flUser, *flPassword, serverAddress, isDefaultRegistry) |
|
| 53 |
- if err != nil {
|
|
| 54 |
- return err |
|
| 55 |
- } |
|
| 56 |
- response, err := cli.client.RegistryLogin(ctx, authConfig) |
|
| 57 |
- if err != nil {
|
|
| 58 |
- return err |
|
| 59 |
- } |
|
| 60 |
- if response.IdentityToken != "" {
|
|
| 61 |
- authConfig.Password = "" |
|
| 62 |
- authConfig.IdentityToken = response.IdentityToken |
|
| 63 |
- } |
|
| 64 |
- if err := storeCredentials(cli.configFile, authConfig); err != nil {
|
|
| 65 |
- return fmt.Errorf("Error saving credentials: %v", err)
|
|
| 66 |
- } |
|
| 67 |
- |
|
| 68 |
- if response.Status != "" {
|
|
| 69 |
- fmt.Fprintln(cli.out, response.Status) |
|
| 70 |
- } |
|
| 71 |
- return nil |
|
| 72 |
-} |
|
| 73 |
- |
|
| 74 |
-func (cli *DockerCli) promptWithDefault(prompt string, configDefault string) {
|
|
| 75 |
- if configDefault == "" {
|
|
| 76 |
- fmt.Fprintf(cli.out, "%s: ", prompt) |
|
| 77 |
- } else {
|
|
| 78 |
- fmt.Fprintf(cli.out, "%s (%s): ", prompt, configDefault) |
|
| 79 |
- } |
|
| 80 |
-} |
|
| 81 |
- |
|
| 82 |
-func (cli *DockerCli) configureAuth(flUser, flPassword, serverAddress string, isDefaultRegistry bool) (types.AuthConfig, error) {
|
|
| 83 |
- authconfig, err := getCredentials(cli.configFile, serverAddress) |
|
| 84 |
- if err != nil {
|
|
| 85 |
- return authconfig, err |
|
| 86 |
- } |
|
| 87 |
- |
|
| 88 |
- // Some links documenting this: |
|
| 89 |
- // - https://code.google.com/archive/p/mintty/issues/56 |
|
| 90 |
- // - https://github.com/docker/docker/issues/15272 |
|
| 91 |
- // - https://mintty.github.io/ (compatibility) |
|
| 92 |
- // Linux will hit this if you attempt `cat | docker login`, and Windows |
|
| 93 |
- // will hit this if you attempt docker login from mintty where stdin |
|
| 94 |
- // is a pipe, not a character based console. |
|
| 95 |
- if flPassword == "" && !cli.isTerminalIn {
|
|
| 96 |
- return authconfig, fmt.Errorf("Error: Cannot perform an interactive logon from a non TTY device")
|
|
| 97 |
- } |
|
| 98 |
- |
|
| 99 |
- authconfig.Username = strings.TrimSpace(authconfig.Username) |
|
| 100 |
- |
|
| 101 |
- if flUser = strings.TrimSpace(flUser); flUser == "" {
|
|
| 102 |
- if isDefaultRegistry {
|
|
| 103 |
- // if this is a defauly registry (docker hub), then display the following message. |
|
| 104 |
- 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.") |
|
| 105 |
- } |
|
| 106 |
- cli.promptWithDefault("Username", authconfig.Username)
|
|
| 107 |
- flUser = readInput(cli.in, cli.out) |
|
| 108 |
- flUser = strings.TrimSpace(flUser) |
|
| 109 |
- if flUser == "" {
|
|
| 110 |
- flUser = authconfig.Username |
|
| 111 |
- } |
|
| 112 |
- } |
|
| 113 |
- if flUser == "" {
|
|
| 114 |
- return authconfig, fmt.Errorf("Error: Non-null Username Required")
|
|
| 115 |
- } |
|
| 116 |
- if flPassword == "" {
|
|
| 117 |
- oldState, err := term.SaveState(cli.inFd) |
|
| 118 |
- if err != nil {
|
|
| 119 |
- return authconfig, err |
|
| 120 |
- } |
|
| 121 |
- fmt.Fprintf(cli.out, "Password: ") |
|
| 122 |
- term.DisableEcho(cli.inFd, oldState) |
|
| 123 |
- |
|
| 124 |
- flPassword = readInput(cli.in, cli.out) |
|
| 125 |
- fmt.Fprint(cli.out, "\n") |
|
| 126 |
- |
|
| 127 |
- term.RestoreTerminal(cli.inFd, oldState) |
|
| 128 |
- if flPassword == "" {
|
|
| 129 |
- return authconfig, fmt.Errorf("Error: Password Required")
|
|
| 130 |
- } |
|
| 131 |
- } |
|
| 132 |
- |
|
| 133 |
- authconfig.Username = flUser |
|
| 134 |
- authconfig.Password = flPassword |
|
| 135 |
- authconfig.ServerAddress = serverAddress |
|
| 136 |
- authconfig.IdentityToken = "" |
|
| 137 |
- |
|
| 138 |
- return authconfig, nil |
|
| 139 |
-} |
|
| 140 |
- |
|
| 141 |
-func readInput(in io.Reader, out io.Writer) string {
|
|
| 142 |
- reader := bufio.NewReader(in) |
|
| 143 |
- line, _, err := reader.ReadLine() |
|
| 144 |
- if err != nil {
|
|
| 145 |
- fmt.Fprintln(out, err.Error()) |
|
| 146 |
- os.Exit(1) |
|
| 147 |
- } |
|
| 148 |
- return string(line) |
|
| 149 |
-} |
|
| 150 |
- |
|
| 151 |
-// getCredentials loads the user credentials from a credentials store. |
|
| 152 |
-// The store is determined by the config file settings. |
|
| 153 |
-func getCredentials(c *configfile.ConfigFile, serverAddress string) (types.AuthConfig, error) {
|
|
| 154 |
- s := loadCredentialsStore(c) |
|
| 155 |
- return s.Get(serverAddress) |
|
| 156 |
-} |
|
| 157 |
- |
|
| 158 |
-func getAllCredentials(c *configfile.ConfigFile) (map[string]types.AuthConfig, error) {
|
|
| 159 |
- s := loadCredentialsStore(c) |
|
| 160 |
- return s.GetAll() |
|
| 161 |
-} |
|
| 162 |
- |
|
| 163 |
-// storeCredentials saves the user credentials in a credentials store. |
|
| 164 |
-// The store is determined by the config file settings. |
|
| 165 |
-func storeCredentials(c *configfile.ConfigFile, auth types.AuthConfig) error {
|
|
| 166 |
- s := loadCredentialsStore(c) |
|
| 167 |
- return s.Store(auth) |
|
| 168 |
-} |
|
| 169 |
- |
|
| 170 |
-// eraseCredentials removes the user credentials from a credentials store. |
|
| 171 |
-// The store is determined by the config file settings. |
|
| 172 |
-func eraseCredentials(c *configfile.ConfigFile, serverAddress string) error {
|
|
| 173 |
- s := loadCredentialsStore(c) |
|
| 174 |
- return s.Erase(serverAddress) |
|
| 175 |
-} |
|
| 176 |
- |
|
| 177 |
-// loadCredentialsStore initializes a new credentials store based |
|
| 178 |
-// in the settings provided in the configuration file. |
|
| 179 |
-func loadCredentialsStore(c *configfile.ConfigFile) credentials.Store {
|
|
| 180 |
- if c.CredentialsStore != "" {
|
|
| 181 |
- return credentials.NewNativeStore(c) |
|
| 182 |
- } |
|
| 183 |
- return credentials.NewFileStore(c) |
|
| 184 |
-} |
| 185 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,43 +0,0 @@ |
| 1 |
-package client |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "fmt" |
|
| 5 |
- |
|
| 6 |
- "golang.org/x/net/context" |
|
| 7 |
- |
|
| 8 |
- Cli "github.com/docker/docker/cli" |
|
| 9 |
- flag "github.com/docker/docker/pkg/mflag" |
|
| 10 |
-) |
|
| 11 |
- |
|
| 12 |
-// CmdLogout logs a user out from a Docker registry. |
|
| 13 |
-// |
|
| 14 |
-// If no server is specified, the user will be logged out from the registry's index server. |
|
| 15 |
-// |
|
| 16 |
-// Usage: docker logout [SERVER] |
|
| 17 |
-func (cli *DockerCli) CmdLogout(args ...string) error {
|
|
| 18 |
- cmd := Cli.Subcmd("logout", []string{"[SERVER]"}, Cli.DockerCommands["logout"].Description+".\nIf no server is specified, the default is defined by the daemon.", true)
|
|
| 19 |
- cmd.Require(flag.Max, 1) |
|
| 20 |
- |
|
| 21 |
- cmd.ParseFlags(args, true) |
|
| 22 |
- |
|
| 23 |
- var serverAddress string |
|
| 24 |
- if len(cmd.Args()) > 0 {
|
|
| 25 |
- serverAddress = cmd.Arg(0) |
|
| 26 |
- } else {
|
|
| 27 |
- serverAddress = cli.electAuthServer(context.Background()) |
|
| 28 |
- } |
|
| 29 |
- |
|
| 30 |
- // check if we're logged in based on the records in the config file |
|
| 31 |
- // which means it couldn't have user/pass cause they may be in the creds store |
|
| 32 |
- if _, ok := cli.configFile.AuthConfigs[serverAddress]; !ok {
|
|
| 33 |
- fmt.Fprintf(cli.out, "Not logged in to %s\n", serverAddress) |
|
| 34 |
- return nil |
|
| 35 |
- } |
|
| 36 |
- |
|
| 37 |
- fmt.Fprintf(cli.out, "Remove login credentials for %s\n", serverAddress) |
|
| 38 |
- if err := eraseCredentials(cli.configFile, serverAddress); err != nil {
|
|
| 39 |
- fmt.Fprintf(cli.out, "WARNING: could not erase credentials: %v\n", err) |
|
| 40 |
- } |
|
| 41 |
- |
|
| 42 |
- return nil |
|
| 43 |
-} |
| 44 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,159 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bufio" |
|
| 4 |
+ "encoding/base64" |
|
| 5 |
+ "encoding/json" |
|
| 6 |
+ "fmt" |
|
| 7 |
+ "io" |
|
| 8 |
+ "os" |
|
| 9 |
+ "runtime" |
|
| 10 |
+ "strings" |
|
| 11 |
+ |
|
| 12 |
+ "golang.org/x/net/context" |
|
| 13 |
+ |
|
| 14 |
+ "github.com/docker/docker/pkg/term" |
|
| 15 |
+ "github.com/docker/docker/registry" |
|
| 16 |
+ "github.com/docker/engine-api/types" |
|
| 17 |
+ registrytypes "github.com/docker/engine-api/types/registry" |
|
| 18 |
+) |
|
| 19 |
+ |
|
| 20 |
+// ElectAuthServer returns the default registry to use (by asking the daemon) |
|
| 21 |
+func (cli *DockerCli) ElectAuthServer(ctx context.Context) string {
|
|
| 22 |
+ // The daemon `/info` endpoint informs us of the default registry being |
|
| 23 |
+ // used. This is essential in cross-platforms environment, where for |
|
| 24 |
+ // example a Linux client might be interacting with a Windows daemon, hence |
|
| 25 |
+ // the default registry URL might be Windows specific. |
|
| 26 |
+ serverAddress := registry.IndexServer |
|
| 27 |
+ if info, err := cli.client.Info(ctx); err != nil {
|
|
| 28 |
+ fmt.Fprintf(cli.out, "Warning: failed to get default registry endpoint from daemon (%v). Using system default: %s\n", err, serverAddress) |
|
| 29 |
+ } else {
|
|
| 30 |
+ serverAddress = info.IndexServerAddress |
|
| 31 |
+ } |
|
| 32 |
+ return serverAddress |
|
| 33 |
+} |
|
| 34 |
+ |
|
| 35 |
+// EncodeAuthToBase64 serializes the auth configuration as JSON base64 payload |
|
| 36 |
+func EncodeAuthToBase64(authConfig types.AuthConfig) (string, error) {
|
|
| 37 |
+ buf, err := json.Marshal(authConfig) |
|
| 38 |
+ if err != nil {
|
|
| 39 |
+ return "", err |
|
| 40 |
+ } |
|
| 41 |
+ return base64.URLEncoding.EncodeToString(buf), nil |
|
| 42 |
+} |
|
| 43 |
+ |
|
| 44 |
+// RegistryAuthenticationPrivilegedFunc return a RequestPrivilegeFunc from the specified registry index info |
|
| 45 |
+// for the given command. |
|
| 46 |
+func (cli *DockerCli) RegistryAuthenticationPrivilegedFunc(index *registrytypes.IndexInfo, cmdName string) types.RequestPrivilegeFunc {
|
|
| 47 |
+ return func() (string, error) {
|
|
| 48 |
+ fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName) |
|
| 49 |
+ indexServer := registry.GetAuthConfigKey(index) |
|
| 50 |
+ authConfig, err := cli.ConfigureAuth("", "", indexServer, false)
|
|
| 51 |
+ if err != nil {
|
|
| 52 |
+ return "", err |
|
| 53 |
+ } |
|
| 54 |
+ return EncodeAuthToBase64(authConfig) |
|
| 55 |
+ } |
|
| 56 |
+} |
|
| 57 |
+ |
|
| 58 |
+func (cli *DockerCli) promptWithDefault(prompt string, configDefault string) {
|
|
| 59 |
+ if configDefault == "" {
|
|
| 60 |
+ fmt.Fprintf(cli.out, "%s: ", prompt) |
|
| 61 |
+ } else {
|
|
| 62 |
+ fmt.Fprintf(cli.out, "%s (%s): ", prompt, configDefault) |
|
| 63 |
+ } |
|
| 64 |
+} |
|
| 65 |
+ |
|
| 66 |
+// ResolveAuthConfig is like registry.ResolveAuthConfig, but if using the |
|
| 67 |
+// default index, it uses the default index name for the daemon's platform, |
|
| 68 |
+// not the client's platform. |
|
| 69 |
+func (cli *DockerCli) ResolveAuthConfig(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig {
|
|
| 70 |
+ configKey := index.Name |
|
| 71 |
+ if index.Official {
|
|
| 72 |
+ configKey = cli.ElectAuthServer(ctx) |
|
| 73 |
+ } |
|
| 74 |
+ |
|
| 75 |
+ a, _ := GetCredentials(cli.configFile, configKey) |
|
| 76 |
+ return a |
|
| 77 |
+} |
|
| 78 |
+ |
|
| 79 |
+// RetrieveAuthConfigs return all credentials. |
|
| 80 |
+func (cli *DockerCli) RetrieveAuthConfigs() map[string]types.AuthConfig {
|
|
| 81 |
+ acs, _ := GetAllCredentials(cli.configFile) |
|
| 82 |
+ return acs |
|
| 83 |
+} |
|
| 84 |
+ |
|
| 85 |
+// ConfigureAuth returns an AuthConfig from the specified user, password and server. |
|
| 86 |
+func (cli *DockerCli) ConfigureAuth(flUser, flPassword, serverAddress string, isDefaultRegistry bool) (types.AuthConfig, error) {
|
|
| 87 |
+ // On Windows, force the use of the regular OS stdin stream. Fixes #14336/#14210 |
|
| 88 |
+ if runtime.GOOS == "windows" {
|
|
| 89 |
+ cli.in = os.Stdin |
|
| 90 |
+ } |
|
| 91 |
+ |
|
| 92 |
+ authconfig, err := GetCredentials(cli.configFile, serverAddress) |
|
| 93 |
+ if err != nil {
|
|
| 94 |
+ return authconfig, err |
|
| 95 |
+ } |
|
| 96 |
+ |
|
| 97 |
+ // Some links documenting this: |
|
| 98 |
+ // - https://code.google.com/archive/p/mintty/issues/56 |
|
| 99 |
+ // - https://github.com/docker/docker/issues/15272 |
|
| 100 |
+ // - https://mintty.github.io/ (compatibility) |
|
| 101 |
+ // Linux will hit this if you attempt `cat | docker login`, and Windows |
|
| 102 |
+ // will hit this if you attempt docker login from mintty where stdin |
|
| 103 |
+ // is a pipe, not a character based console. |
|
| 104 |
+ if flPassword == "" && !cli.isTerminalIn {
|
|
| 105 |
+ return authconfig, fmt.Errorf("Error: Cannot perform an interactive logon from a non TTY device")
|
|
| 106 |
+ } |
|
| 107 |
+ |
|
| 108 |
+ authconfig.Username = strings.TrimSpace(authconfig.Username) |
|
| 109 |
+ |
|
| 110 |
+ if flUser = strings.TrimSpace(flUser); flUser == "" {
|
|
| 111 |
+ if isDefaultRegistry {
|
|
| 112 |
+ // if this is a defauly registry (docker hub), then display the following message. |
|
| 113 |
+ 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.") |
|
| 114 |
+ } |
|
| 115 |
+ cli.promptWithDefault("Username", authconfig.Username)
|
|
| 116 |
+ flUser = readInput(cli.in, cli.out) |
|
| 117 |
+ flUser = strings.TrimSpace(flUser) |
|
| 118 |
+ if flUser == "" {
|
|
| 119 |
+ flUser = authconfig.Username |
|
| 120 |
+ } |
|
| 121 |
+ } |
|
| 122 |
+ if flUser == "" {
|
|
| 123 |
+ return authconfig, fmt.Errorf("Error: Non-null Username Required")
|
|
| 124 |
+ } |
|
| 125 |
+ if flPassword == "" {
|
|
| 126 |
+ oldState, err := term.SaveState(cli.inFd) |
|
| 127 |
+ if err != nil {
|
|
| 128 |
+ return authconfig, err |
|
| 129 |
+ } |
|
| 130 |
+ fmt.Fprintf(cli.out, "Password: ") |
|
| 131 |
+ term.DisableEcho(cli.inFd, oldState) |
|
| 132 |
+ |
|
| 133 |
+ flPassword = readInput(cli.in, cli.out) |
|
| 134 |
+ fmt.Fprint(cli.out, "\n") |
|
| 135 |
+ |
|
| 136 |
+ term.RestoreTerminal(cli.inFd, oldState) |
|
| 137 |
+ if flPassword == "" {
|
|
| 138 |
+ return authconfig, fmt.Errorf("Error: Password Required")
|
|
| 139 |
+ } |
|
| 140 |
+ } |
|
| 141 |
+ |
|
| 142 |
+ authconfig.Username = flUser |
|
| 143 |
+ authconfig.Password = flPassword |
|
| 144 |
+ authconfig.ServerAddress = serverAddress |
|
| 145 |
+ authconfig.IdentityToken = "" |
|
| 146 |
+ |
|
| 147 |
+ return authconfig, nil |
|
| 148 |
+} |
|
| 149 |
+ |
|
| 150 |
+func readInput(in io.Reader, out io.Writer) string {
|
|
| 151 |
+ reader := bufio.NewReader(in) |
|
| 152 |
+ line, _, err := reader.ReadLine() |
|
| 153 |
+ if err != nil {
|
|
| 154 |
+ fmt.Fprintln(out, err.Error()) |
|
| 155 |
+ os.Exit(1) |
|
| 156 |
+ } |
|
| 157 |
+ return string(line) |
|
| 158 |
+} |
| 0 | 159 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,81 @@ |
| 0 |
+package registry |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ |
|
| 5 |
+ "golang.org/x/net/context" |
|
| 6 |
+ |
|
| 7 |
+ "github.com/docker/docker/api/client" |
|
| 8 |
+ "github.com/docker/docker/cli" |
|
| 9 |
+ "github.com/spf13/cobra" |
|
| 10 |
+) |
|
| 11 |
+ |
|
| 12 |
+type loginOptions struct {
|
|
| 13 |
+ serverAddress string |
|
| 14 |
+ user string |
|
| 15 |
+ password string |
|
| 16 |
+ email string |
|
| 17 |
+} |
|
| 18 |
+ |
|
| 19 |
+// NewLoginCommand creates a new `docker login` command |
|
| 20 |
+func NewLoginCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
| 21 |
+ var opts loginOptions |
|
| 22 |
+ |
|
| 23 |
+ cmd := &cobra.Command{
|
|
| 24 |
+ Use: "login [OPTIONS] [SERVER]", |
|
| 25 |
+ Short: "Log in to a Docker registry.", |
|
| 26 |
+ Long: "Log in to a Docker registry.\nIf no server is specified, the default is defined by the daemon.", |
|
| 27 |
+ Args: cli.RequiresMaxArgs(1), |
|
| 28 |
+ RunE: func(cmd *cobra.Command, args []string) error {
|
|
| 29 |
+ if len(args) > 0 {
|
|
| 30 |
+ opts.serverAddress = args[0] |
|
| 31 |
+ } |
|
| 32 |
+ return runLogin(dockerCli, opts) |
|
| 33 |
+ }, |
|
| 34 |
+ } |
|
| 35 |
+ |
|
| 36 |
+ flags := cmd.Flags() |
|
| 37 |
+ |
|
| 38 |
+ flags.StringVarP(&opts.user, "username", "u", "", "Username") |
|
| 39 |
+ flags.StringVarP(&opts.password, "password", "p", "", "Password") |
|
| 40 |
+ |
|
| 41 |
+ // Deprecated in 1.11: Should be removed in docker 1.13 |
|
| 42 |
+ flags.StringVarP(&opts.email, "email", "e", "", "Email") |
|
| 43 |
+ flags.MarkDeprecated("email", "will be removed in 1.13.")
|
|
| 44 |
+ |
|
| 45 |
+ return cmd |
|
| 46 |
+} |
|
| 47 |
+ |
|
| 48 |
+func runLogin(dockerCli *client.DockerCli, opts loginOptions) error {
|
|
| 49 |
+ ctx := context.Background() |
|
| 50 |
+ clnt := dockerCli.Client() |
|
| 51 |
+ |
|
| 52 |
+ var serverAddress string |
|
| 53 |
+ var isDefaultRegistry bool |
|
| 54 |
+ if opts.serverAddress != "" {
|
|
| 55 |
+ serverAddress = opts.serverAddress |
|
| 56 |
+ } else {
|
|
| 57 |
+ serverAddress = dockerCli.ElectAuthServer(ctx) |
|
| 58 |
+ isDefaultRegistry = true |
|
| 59 |
+ } |
|
| 60 |
+ authConfig, err := dockerCli.ConfigureAuth(opts.user, opts.password, serverAddress, isDefaultRegistry) |
|
| 61 |
+ if err != nil {
|
|
| 62 |
+ return err |
|
| 63 |
+ } |
|
| 64 |
+ response, err := clnt.RegistryLogin(ctx, authConfig) |
|
| 65 |
+ if err != nil {
|
|
| 66 |
+ return err |
|
| 67 |
+ } |
|
| 68 |
+ if response.IdentityToken != "" {
|
|
| 69 |
+ authConfig.Password = "" |
|
| 70 |
+ authConfig.IdentityToken = response.IdentityToken |
|
| 71 |
+ } |
|
| 72 |
+ if err := client.StoreCredentials(dockerCli.ConfigFile(), authConfig); err != nil {
|
|
| 73 |
+ return fmt.Errorf("Error saving credentials: %v", err)
|
|
| 74 |
+ } |
|
| 75 |
+ |
|
| 76 |
+ if response.Status != "" {
|
|
| 77 |
+ fmt.Fprintln(dockerCli.Out(), response.Status) |
|
| 78 |
+ } |
|
| 79 |
+ return nil |
|
| 80 |
+} |
| 0 | 81 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,52 @@ |
| 0 |
+package registry |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ |
|
| 5 |
+ "golang.org/x/net/context" |
|
| 6 |
+ |
|
| 7 |
+ "github.com/docker/docker/api/client" |
|
| 8 |
+ "github.com/docker/docker/cli" |
|
| 9 |
+ "github.com/spf13/cobra" |
|
| 10 |
+) |
|
| 11 |
+ |
|
| 12 |
+// NewLogoutCommand creates a new `docker login` command |
|
| 13 |
+func NewLogoutCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
| 14 |
+ cmd := &cobra.Command{
|
|
| 15 |
+ Use: "logout [SERVER]", |
|
| 16 |
+ Short: "Log out from a Docker registry.", |
|
| 17 |
+ Long: "Log out from a Docker registry.\nIf no server is specified, the default is defined by the daemon.", |
|
| 18 |
+ Args: cli.RequiresMaxArgs(1), |
|
| 19 |
+ RunE: func(cmd *cobra.Command, args []string) error {
|
|
| 20 |
+ var serverAddress string |
|
| 21 |
+ if len(args) > 0 {
|
|
| 22 |
+ serverAddress = args[0] |
|
| 23 |
+ } |
|
| 24 |
+ return runLogout(dockerCli, serverAddress) |
|
| 25 |
+ }, |
|
| 26 |
+ } |
|
| 27 |
+ |
|
| 28 |
+ return cmd |
|
| 29 |
+} |
|
| 30 |
+ |
|
| 31 |
+func runLogout(dockerCli *client.DockerCli, serverAddress string) error {
|
|
| 32 |
+ ctx := context.Background() |
|
| 33 |
+ |
|
| 34 |
+ if serverAddress == "" {
|
|
| 35 |
+ serverAddress = dockerCli.ElectAuthServer(ctx) |
|
| 36 |
+ } |
|
| 37 |
+ |
|
| 38 |
+ // check if we're logged in based on the records in the config file |
|
| 39 |
+ // which means it couldn't have user/pass cause they may be in the creds store |
|
| 40 |
+ if _, ok := dockerCli.ConfigFile().AuthConfigs[serverAddress]; !ok {
|
|
| 41 |
+ fmt.Fprintf(dockerCli.Out(), "Not logged in to %s\n", serverAddress) |
|
| 42 |
+ return nil |
|
| 43 |
+ } |
|
| 44 |
+ |
|
| 45 |
+ fmt.Fprintf(dockerCli.Out(), "Remove login credentials for %s\n", serverAddress) |
|
| 46 |
+ if err := client.EraseCredentials(dockerCli.ConfigFile(), serverAddress); err != nil {
|
|
| 47 |
+ fmt.Fprintf(dockerCli.Out(), "WARNING: could not erase credentials: %v\n", err) |
|
| 48 |
+ } |
|
| 49 |
+ |
|
| 50 |
+ return nil |
|
| 51 |
+} |
| ... | ... |
@@ -1,8 +1,6 @@ |
| 1 | 1 |
package client |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "encoding/base64" |
|
| 5 |
- "encoding/json" |
|
| 6 | 4 |
"fmt" |
| 7 | 5 |
"io" |
| 8 | 6 |
"io/ioutil" |
| ... | ... |
@@ -17,49 +15,10 @@ import ( |
| 17 | 17 |
"github.com/Sirupsen/logrus" |
| 18 | 18 |
"github.com/docker/docker/pkg/signal" |
| 19 | 19 |
"github.com/docker/docker/pkg/term" |
| 20 |
- "github.com/docker/docker/registry" |
|
| 21 | 20 |
"github.com/docker/engine-api/client" |
| 22 | 21 |
"github.com/docker/engine-api/types" |
| 23 |
- registrytypes "github.com/docker/engine-api/types/registry" |
|
| 24 | 22 |
) |
| 25 | 23 |
|
| 26 |
-func (cli *DockerCli) electAuthServer(ctx context.Context) string {
|
|
| 27 |
- // The daemon `/info` endpoint informs us of the default registry being |
|
| 28 |
- // used. This is essential in cross-platforms environment, where for |
|
| 29 |
- // example a Linux client might be interacting with a Windows daemon, hence |
|
| 30 |
- // the default registry URL might be Windows specific. |
|
| 31 |
- serverAddress := registry.IndexServer |
|
| 32 |
- if info, err := cli.client.Info(ctx); err != nil {
|
|
| 33 |
- fmt.Fprintf(cli.out, "Warning: failed to get default registry endpoint from daemon (%v). Using system default: %s\n", err, serverAddress) |
|
| 34 |
- } else {
|
|
| 35 |
- serverAddress = info.IndexServerAddress |
|
| 36 |
- } |
|
| 37 |
- return serverAddress |
|
| 38 |
-} |
|
| 39 |
- |
|
| 40 |
-// EncodeAuthToBase64 serializes the auth configuration as JSON base64 payload |
|
| 41 |
-func EncodeAuthToBase64(authConfig types.AuthConfig) (string, error) {
|
|
| 42 |
- buf, err := json.Marshal(authConfig) |
|
| 43 |
- if err != nil {
|
|
| 44 |
- return "", err |
|
| 45 |
- } |
|
| 46 |
- return base64.URLEncoding.EncodeToString(buf), nil |
|
| 47 |
-} |
|
| 48 |
- |
|
| 49 |
-// RegistryAuthenticationPrivilegedFunc return a RequestPrivilegeFunc from the specified registry index info |
|
| 50 |
-// for the given command. |
|
| 51 |
-func (cli *DockerCli) RegistryAuthenticationPrivilegedFunc(index *registrytypes.IndexInfo, cmdName string) types.RequestPrivilegeFunc {
|
|
| 52 |
- return func() (string, error) {
|
|
| 53 |
- fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName) |
|
| 54 |
- indexServer := registry.GetAuthConfigKey(index) |
|
| 55 |
- authConfig, err := cli.configureAuth("", "", indexServer, false)
|
|
| 56 |
- if err != nil {
|
|
| 57 |
- return "", err |
|
| 58 |
- } |
|
| 59 |
- return EncodeAuthToBase64(authConfig) |
|
| 60 |
- } |
|
| 61 |
-} |
|
| 62 |
- |
|
| 63 | 24 |
func (cli *DockerCli) resizeTty(ctx context.Context, id string, isExec bool) {
|
| 64 | 25 |
height, width := cli.GetTtySize() |
| 65 | 26 |
cli.ResizeTtyTo(ctx, id, height, width, isExec) |
| ... | ... |
@@ -189,26 +148,7 @@ func CopyToFile(outfile string, r io.Reader) error {
|
| 189 | 189 |
return nil |
| 190 | 190 |
} |
| 191 | 191 |
|
| 192 |
-// ResolveAuthConfig is like registry.ResolveAuthConfig, but if using the |
|
| 193 |
-// default index, it uses the default index name for the daemon's platform, |
|
| 194 |
-// not the client's platform. |
|
| 195 |
-func (cli *DockerCli) ResolveAuthConfig(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig {
|
|
| 196 |
- configKey := index.Name |
|
| 197 |
- if index.Official {
|
|
| 198 |
- configKey = cli.electAuthServer(ctx) |
|
| 199 |
- } |
|
| 200 |
- |
|
| 201 |
- a, _ := getCredentials(cli.configFile, configKey) |
|
| 202 |
- return a |
|
| 203 |
-} |
|
| 204 |
- |
|
| 205 |
-// RetrieveAuthConfigs return all credentials. |
|
| 206 |
-func (cli *DockerCli) RetrieveAuthConfigs() map[string]types.AuthConfig {
|
|
| 207 |
- acs, _ := getAllCredentials(cli.configFile) |
|
| 208 |
- return acs |
|
| 209 |
-} |
|
| 210 |
- |
|
| 211 |
-// ForwardAllSignals forwards signals to the container |
|
| 192 |
+// ForwardAllSignals forwards signals to the contianer |
|
| 212 | 193 |
// TODO: this can be unexported again once all container commands are under |
| 213 | 194 |
// api/client/container |
| 214 | 195 |
func (cli *DockerCli) ForwardAllSignals(ctx context.Context, cid string) chan os.Signal {
|
| ... | ... |
@@ -5,6 +5,7 @@ import ( |
| 5 | 5 |
"github.com/docker/docker/api/client/container" |
| 6 | 6 |
"github.com/docker/docker/api/client/image" |
| 7 | 7 |
"github.com/docker/docker/api/client/network" |
| 8 |
+ "github.com/docker/docker/api/client/registry" |
|
| 8 | 9 |
"github.com/docker/docker/api/client/system" |
| 9 | 10 |
"github.com/docker/docker/api/client/volume" |
| 10 | 11 |
"github.com/docker/docker/cli" |
| ... | ... |
@@ -64,6 +65,8 @@ func NewCobraAdaptor(clientFlags *cliflags.ClientFlags) CobraAdaptor {
|
| 64 | 64 |
image.NewTagCommand(dockerCli), |
| 65 | 65 |
network.NewNetworkCommand(dockerCli), |
| 66 | 66 |
system.NewEventsCommand(dockerCli), |
| 67 |
+ registry.NewLoginCommand(dockerCli), |
|
| 68 |
+ registry.NewLogoutCommand(dockerCli), |
|
| 67 | 69 |
system.NewVersionCommand(dockerCli), |
| 68 | 70 |
volume.NewVolumeCommand(dockerCli), |
| 69 | 71 |
) |
| ... | ... |
@@ -13,8 +13,6 @@ var DockerCommandUsage = []Command{
|
| 13 | 13 |
{"exec", "Run a command in a running container"},
|
| 14 | 14 |
{"info", "Display system-wide information"},
|
| 15 | 15 |
{"inspect", "Return low-level information on a container or image"},
|
| 16 |
- {"login", "Log in to a Docker registry"},
|
|
| 17 |
- {"logout", "Log out from a Docker registry"},
|
|
| 18 | 16 |
{"ps", "List containers"},
|
| 19 | 17 |
{"pull", "Pull an image or a repository from a registry"},
|
| 20 | 18 |
{"push", "Push an image or a repository to a registry"},
|