Skip to content

Commit eb6546b

Browse files
authored
Merge pull request #5774 from Benehiko/improve-login-text
login: improve text on already authenticated and on OAuth login
2 parents 9005f36 + 6d7afd4 commit eb6546b

File tree

5 files changed

+61
-37
lines changed

5 files changed

+61
-37
lines changed

cli/command/registry.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ import (
1313
configtypes "github.com/docker/cli/cli/config/types"
1414
"github.com/docker/cli/cli/hints"
1515
"github.com/docker/cli/cli/streams"
16+
"github.com/docker/cli/internal/tui"
1617
registrytypes "github.com/docker/docker/api/types/registry"
1718
"github.com/docker/docker/registry"
19+
"github.com/morikuni/aec"
1820
"github.com/pkg/errors"
1921
)
2022

@@ -178,6 +180,9 @@ func PromptUserForCredentials(ctx context.Context, cli Cli, argUser, argPassword
178180
}
179181
}()
180182

183+
out := tui.NewOutput(cli.Err())
184+
out.PrintNote("A Personal Access Token (PAT) can be used instead.\n" +
185+
"To create a PAT, visit " + aec.Underline.Apply("https://app.docker.com/settings") + "\n\n")
181186
argPassword, err = PromptForInput(ctx, cli.In(), cli.Out(), "Password: ")
182187
if err != nil {
183188
return registrytypes.AuthConfig{}, err

cli/command/registry/login.go

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/docker/cli/cli/config/configfile"
1515
configtypes "github.com/docker/cli/cli/config/types"
1616
"github.com/docker/cli/cli/internal/oauth/manager"
17+
"github.com/docker/cli/internal/tui"
1718
registrytypes "github.com/docker/docker/api/types/registry"
1819
"github.com/docker/docker/client"
1920
"github.com/docker/docker/errdefs"
@@ -30,7 +31,7 @@ type loginOptions struct {
3031
}
3132

3233
// NewLoginCommand creates a new `docker login` command
33-
func NewLoginCommand(dockerCli command.Cli) *cobra.Command {
34+
func NewLoginCommand(dockerCLI command.Cli) *cobra.Command {
3435
var opts loginOptions
3536

3637
cmd := &cobra.Command{
@@ -42,7 +43,7 @@ func NewLoginCommand(dockerCli command.Cli) *cobra.Command {
4243
if len(args) > 0 {
4344
opts.serverAddress = args[0]
4445
}
45-
return runLogin(cmd.Context(), dockerCli, opts)
46+
return runLogin(cmd.Context(), dockerCLI, opts)
4647
},
4748
Annotations: map[string]string{
4849
"category-top": "8",
@@ -53,15 +54,15 @@ func NewLoginCommand(dockerCli command.Cli) *cobra.Command {
5354
flags := cmd.Flags()
5455

5556
flags.StringVarP(&opts.user, "username", "u", "", "Username")
56-
flags.StringVarP(&opts.password, "password", "p", "", "Password")
57-
flags.BoolVar(&opts.passwordStdin, "password-stdin", false, "Take the password from stdin")
57+
flags.StringVarP(&opts.password, "password", "p", "", "Password or Personal Access Token (PAT)")
58+
flags.BoolVar(&opts.passwordStdin, "password-stdin", false, "Take the Password or Personal Access Token (PAT) from stdin")
5859

5960
return cmd
6061
}
6162

62-
func verifyLoginOptions(dockerCli command.Cli, opts *loginOptions) error {
63+
func verifyLoginOptions(dockerCLI command.Cli, opts *loginOptions) error {
6364
if opts.password != "" {
64-
_, _ = fmt.Fprintln(dockerCli.Err(), "WARNING! Using --password via the CLI is insecure. Use --password-stdin.")
65+
_, _ = fmt.Fprintln(dockerCLI.Err(), "WARNING! Using --password via the CLI is insecure. Use --password-stdin.")
6566
if opts.passwordStdin {
6667
return errors.New("--password and --password-stdin are mutually exclusive")
6768
}
@@ -72,7 +73,7 @@ func verifyLoginOptions(dockerCli command.Cli, opts *loginOptions) error {
7273
return errors.New("Must provide --username with --password-stdin")
7374
}
7475

75-
contents, err := io.ReadAll(dockerCli.In())
76+
contents, err := io.ReadAll(dockerCLI.In())
7677
if err != nil {
7778
return err
7879
}
@@ -83,8 +84,8 @@ func verifyLoginOptions(dockerCli command.Cli, opts *loginOptions) error {
8384
return nil
8485
}
8586

86-
func runLogin(ctx context.Context, dockerCli command.Cli, opts loginOptions) error {
87-
if err := verifyLoginOptions(dockerCli, &opts); err != nil {
87+
func runLogin(ctx context.Context, dockerCLI command.Cli, opts loginOptions) error {
88+
if err := verifyLoginOptions(dockerCLI, &opts); err != nil {
8889
return err
8990
}
9091
var (
@@ -99,28 +100,38 @@ func runLogin(ctx context.Context, dockerCli command.Cli, opts loginOptions) err
99100
isDefaultRegistry := serverAddress == registry.IndexServer
100101

101102
// attempt login with current (stored) credentials
102-
authConfig, err := command.GetDefaultAuthConfig(dockerCli.ConfigFile(), opts.user == "" && opts.password == "", serverAddress, isDefaultRegistry)
103+
authConfig, err := command.GetDefaultAuthConfig(dockerCLI.ConfigFile(), opts.user == "" && opts.password == "", serverAddress, isDefaultRegistry)
103104
if err == nil && authConfig.Username != "" && authConfig.Password != "" {
104-
msg, err = loginWithStoredCredentials(ctx, dockerCli, authConfig)
105+
msg, err = loginWithStoredCredentials(ctx, dockerCLI, authConfig)
105106
}
106107

107108
// if we failed to authenticate with stored credentials (or didn't have stored credentials),
108109
// prompt the user for new credentials
109110
if err != nil || authConfig.Username == "" || authConfig.Password == "" {
110-
msg, err = loginUser(ctx, dockerCli, opts, authConfig.Username, authConfig.ServerAddress)
111+
msg, err = loginUser(ctx, dockerCLI, opts, authConfig.Username, authConfig.ServerAddress)
111112
if err != nil {
112113
return err
113114
}
114115
}
115116

116117
if msg != "" {
117-
_, _ = fmt.Fprintln(dockerCli.Out(), msg)
118+
_, _ = fmt.Fprintln(dockerCLI.Out(), msg)
118119
}
119120
return nil
120121
}
121122

122123
func loginWithStoredCredentials(ctx context.Context, dockerCLI command.Cli, authConfig registrytypes.AuthConfig) (msg string, _ error) {
123-
_, _ = fmt.Fprintln(dockerCLI.Out(), "Authenticating with existing credentials...")
124+
_, _ = fmt.Fprintf(dockerCLI.Err(), "Authenticating with existing credentials...")
125+
if authConfig.Username != "" {
126+
_, _ = fmt.Fprintf(dockerCLI.Err(), " [Username: %s]", authConfig.Username)
127+
}
128+
_, _ = fmt.Fprint(dockerCLI.Err(), "\n")
129+
130+
out := tui.NewOutput(dockerCLI.Err())
131+
out.PrintNote("To login with a different account, run 'docker logout' followed by 'docker login'")
132+
133+
_, _ = fmt.Fprint(dockerCLI.Err(), "\n\n")
134+
124135
response, err := dockerCLI.Client().RegistryLogin(ctx, authConfig)
125136
if err != nil {
126137
if errdefs.IsUnauthorized(err) {
@@ -155,41 +166,41 @@ func isOauthLoginDisabled() bool {
155166
return false
156167
}
157168

158-
func loginUser(ctx context.Context, dockerCli command.Cli, opts loginOptions, defaultUsername, serverAddress string) (msg string, _ error) {
169+
func loginUser(ctx context.Context, dockerCLI command.Cli, opts loginOptions, defaultUsername, serverAddress string) (msg string, _ error) {
159170
// Some links documenting this:
160171
// - https://code.google.com/archive/p/mintty/issues/56
161172
// - https://github.com/docker/docker/issues/15272
162173
// - https://mintty.github.io/ (compatibility)
163174
// Linux will hit this if you attempt `cat | docker login`, and Windows
164175
// will hit this if you attempt docker login from mintty where stdin
165176
// is a pipe, not a character based console.
166-
if (opts.user == "" || opts.password == "") && !dockerCli.In().IsTerminal() {
177+
if (opts.user == "" || opts.password == "") && !dockerCLI.In().IsTerminal() {
167178
return "", errors.Errorf("Error: Cannot perform an interactive login from a non TTY device")
168179
}
169180

170181
// If we're logging into the index server and the user didn't provide a username or password, use the device flow
171182
if serverAddress == registry.IndexServer && opts.user == "" && opts.password == "" && !isOauthLoginDisabled() {
172183
var err error
173-
msg, err = loginWithDeviceCodeFlow(ctx, dockerCli)
184+
msg, err = loginWithDeviceCodeFlow(ctx, dockerCLI)
174185
// if the error represents a failure to initiate the device-code flow,
175186
// then we fallback to regular cli credentials login
176187
if !errors.Is(err, manager.ErrDeviceLoginStartFail) {
177188
return msg, err
178189
}
179-
_, _ = fmt.Fprint(dockerCli.Err(), "Failed to start web-based login - falling back to command line login...\n\n")
190+
_, _ = fmt.Fprint(dockerCLI.Err(), "Failed to start web-based login - falling back to command line login...\n\n")
180191
}
181192

182-
return loginWithUsernameAndPassword(ctx, dockerCli, opts, defaultUsername, serverAddress)
193+
return loginWithUsernameAndPassword(ctx, dockerCLI, opts, defaultUsername, serverAddress)
183194
}
184195

185-
func loginWithUsernameAndPassword(ctx context.Context, dockerCli command.Cli, opts loginOptions, defaultUsername, serverAddress string) (msg string, _ error) {
196+
func loginWithUsernameAndPassword(ctx context.Context, dockerCLI command.Cli, opts loginOptions, defaultUsername, serverAddress string) (msg string, _ error) {
186197
// Prompt user for credentials
187-
authConfig, err := command.PromptUserForCredentials(ctx, dockerCli, opts.user, opts.password, defaultUsername, serverAddress)
198+
authConfig, err := command.PromptUserForCredentials(ctx, dockerCLI, opts.user, opts.password, defaultUsername, serverAddress)
188199
if err != nil {
189200
return "", err
190201
}
191202

192-
response, err := loginWithRegistry(ctx, dockerCli.Client(), authConfig)
203+
response, err := loginWithRegistry(ctx, dockerCLI.Client(), authConfig)
193204
if err != nil {
194205
return "", err
195206
}
@@ -198,26 +209,26 @@ func loginWithUsernameAndPassword(ctx context.Context, dockerCli command.Cli, op
198209
authConfig.Password = ""
199210
authConfig.IdentityToken = response.IdentityToken
200211
}
201-
if err = storeCredentials(dockerCli.ConfigFile(), authConfig); err != nil {
212+
if err = storeCredentials(dockerCLI.ConfigFile(), authConfig); err != nil {
202213
return "", err
203214
}
204215

205216
return response.Status, nil
206217
}
207218

208-
func loginWithDeviceCodeFlow(ctx context.Context, dockerCli command.Cli) (msg string, _ error) {
209-
store := dockerCli.ConfigFile().GetCredentialsStore(registry.IndexServer)
210-
authConfig, err := manager.NewManager(store).LoginDevice(ctx, dockerCli.Err())
219+
func loginWithDeviceCodeFlow(ctx context.Context, dockerCLI command.Cli) (msg string, _ error) {
220+
store := dockerCLI.ConfigFile().GetCredentialsStore(registry.IndexServer)
221+
authConfig, err := manager.NewManager(store).LoginDevice(ctx, dockerCLI.Err())
211222
if err != nil {
212223
return "", err
213224
}
214225

215-
response, err := loginWithRegistry(ctx, dockerCli.Client(), registrytypes.AuthConfig(*authConfig))
226+
response, err := loginWithRegistry(ctx, dockerCLI.Client(), registrytypes.AuthConfig(*authConfig))
216227
if err != nil {
217228
return "", err
218229
}
219230

220-
if err = storeCredentials(dockerCli.ConfigFile(), registrytypes.AuthConfig(*authConfig)); err != nil {
231+
if err = storeCredentials(dockerCLI.ConfigFile(), registrytypes.AuthConfig(*authConfig)); err != nil {
221232
return "", err
222233
}
223234

cli/command/registry/login_test.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,12 @@ func TestLoginWithCredStoreCreds(t *testing.T) {
6161
}{
6262
{
6363
inputAuthConfig: registrytypes.AuthConfig{},
64-
expectedMsg: "Authenticating with existing credentials...\n",
6564
},
6665
{
6766
inputAuthConfig: registrytypes.AuthConfig{
6867
Username: unknownUser,
6968
},
7069
expectedErr: errUnknownUser,
71-
expectedMsg: "Authenticating with existing credentials...\n",
7270
expectedErrMsg: fmt.Sprintf("Login did not succeed, error: %s\n", errUnknownUser),
7371
},
7472
}
@@ -83,7 +81,7 @@ func TestLoginWithCredStoreCreds(t *testing.T) {
8381
assert.NilError(t, err)
8482
}
8583
assert.Check(t, is.Equal(tc.expectedMsg, cli.OutBuffer().String()))
86-
assert.Check(t, is.Equal(tc.expectedErrMsg, cli.ErrBuffer().String()))
84+
assert.Check(t, is.Contains(cli.ErrBuffer().String(), tc.expectedErrMsg))
8785
cli.ErrBuffer().Reset()
8886
cli.OutBuffer().Reset()
8987
}

cli/internal/oauth/manager/manager.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313
"github.com/docker/cli/cli/config/types"
1414
"github.com/docker/cli/cli/internal/oauth"
1515
"github.com/docker/cli/cli/internal/oauth/api"
16+
"github.com/docker/cli/cli/streams"
17+
"github.com/docker/cli/internal/tui"
1618
"github.com/docker/docker/registry"
1719
"github.com/morikuni/aec"
1820
"github.com/sirupsen/logrus"
@@ -93,7 +95,15 @@ func (m *OAuthManager) LoginDevice(ctx context.Context, w io.Writer) (*types.Aut
9395
}
9496

9597
_, _ = fmt.Fprintln(w, aec.Bold.Apply("\nUSING WEB-BASED LOGIN"))
96-
_, _ = fmt.Fprintln(w, "To sign in with credentials on the command line, use 'docker login -u <username>'")
98+
99+
var out tui.Output
100+
switch stream := w.(type) {
101+
case *streams.Out:
102+
out = tui.NewOutput(stream)
103+
default:
104+
out = tui.NewOutput(streams.NewOut(w))
105+
}
106+
out.PrintNote("To sign in with credentials on the command line, use 'docker login -u <username>'\n")
97107
_, _ = fmt.Fprintf(w, "\nYour one-time device confirmation code is: "+aec.Bold.Apply("%s\n"), state.UserCode)
98108
_, _ = fmt.Fprintf(w, aec.Bold.Apply("Press ENTER")+" to open your browser or submit your device code here: "+aec.Underline.Apply("%s\n"), strings.Split(state.VerificationURI, "?")[0])
99109

docs/reference/commandline/login.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ Defaults to Docker Hub if no server is specified.
66

77
### Options
88

9-
| Name | Type | Default | Description |
10-
|:---------------------------------------------|:---------|:--------|:-----------------------------|
11-
| `-p`, `--password` | `string` | | Password |
12-
| [`--password-stdin`](#password-stdin) | `bool` | | Take the password from stdin |
13-
| [`-u`](#username), [`--username`](#username) | `string` | | Username |
9+
| Name | Type | Default | Description |
10+
|:---------------------------------------------|:---------|:--------|:------------------------------------------------------------|
11+
| `-p`, `--password` | `string` | | Password or Personal Access Token (PAT) |
12+
| [`--password-stdin`](#password-stdin) | `bool` | | Take the Password or Personal Access Token (PAT) from stdin |
13+
| [`-u`](#username), [`--username`](#username) | `string` | | Username |
1414

1515

1616
<!---MARKER_GEN_END-->

0 commit comments

Comments
 (0)