Skip to content

Commit 6d27459

Browse files
authored
Merge pull request #7414 from thaJeztah/interactive_login_nits
internal/oauth: assorted fixes and cleanups
2 parents 4e09976 + df9a0f4 commit 6d27459

4 files changed

Lines changed: 37 additions & 25 deletions

File tree

‎cli/command/registry/login.go‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/docker/cli/internal/tui"
2020
registrytypes "github.com/moby/moby/api/types/registry"
2121
"github.com/moby/moby/client"
22+
"github.com/sirupsen/logrus"
2223
"github.com/spf13/cobra"
2324
"github.com/spf13/pflag"
2425
)
@@ -226,7 +227,7 @@ func loginWithStoredCredentials(ctx context.Context, dockerCLI command.Cli, auth
226227
return resp.Auth.Status, err
227228
}
228229

229-
func loginUser(ctx context.Context, dockerCLI command.Cli, opts loginOptions, defaultUsername, serverAddress string) (msg string, _ error) {
230+
func loginUser(ctx context.Context, dockerCLI command.Cli, opts loginOptions, defaultUsername, serverAddress string) (message string, _ error) {
230231
// Some links documenting this:
231232
// - https://code.google.com/archive/p/mintty/issues/56
232233
// - https://github.com/docker/docker/issues/15272
@@ -240,13 +241,12 @@ func loginUser(ctx context.Context, dockerCLI command.Cli, opts loginOptions, de
240241

241242
// If we're logging into the index server and the user didn't provide a username or password, use the device flow
242243
if serverAddress == registry.IndexServer && opts.user == "" && opts.password == "" {
243-
var err error
244-
msg, err = loginWithDeviceCodeFlow(ctx, dockerCLI)
245-
// if the error represents a failure to initiate the device-code flow,
246-
// then we fallback to regular cli credentials login
247-
if !errors.Is(err, manager.ErrDeviceLoginStartFail) {
244+
msg, err := loginWithDeviceCodeFlow(ctx, dockerCLI)
245+
if err == nil || !errors.Is(err, manager.ErrDeviceLoginStartFail) {
248246
return msg, err
249247
}
248+
249+
logrus.WithError(err).Debug("failed to start web-based login")
250250
_, _ = fmt.Fprint(dockerCLI.Err(), "Failed to start web-based login - falling back to command line login...\n\n")
251251
}
252252

‎docs/reference/commandline/login.md‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -180,13 +180,12 @@ code, and authenticate.
180180
```console
181181
$ docker login
182182

183-
USING WEB-BASED LOGIN
184-
To sign in with credentials on the command line, use 'docker login -u <username>'
183+
Using web-based login. To sign in with credentials on the command line, use 'docker login -u <username>'
185184

186185
Your one-time device confirmation code is: LNFR-PGCJ
187-
Press ENTER to open your browser or submit your device code here: https://login.docker.com/activate
186+
Open this URL to authenticate: https://login.docker.com/activate?user_code=LNFR-PGCJ
188187

189-
Waiting for authentication in the browser…
188+
Press ENTER to open the URL in your browser, or Ctrl+C to cancel.
190189
```
191190

192191
After entering the code in your browser, you are authenticated to Docker Hub

‎e2e/registry/login_test.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,5 @@ func TestOauthLogin(t *testing.T) {
3030
assert.NilError(t, err)
3131

3232
output, _ := io.ReadAll(p)
33-
assert.Check(t, strings.Contains(string(output), "USING WEB-BASED LOGIN"), string(output))
33+
assert.Check(t, strings.Contains(string(output), "Using web-based login"), string(output))
3434
}

‎internal/oauth/manager/manager.go‎

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"io"
99
"os"
10+
"os/exec"
1011
"strings"
1112

1213
"github.com/docker/cli/cli/config/credentials"
@@ -17,8 +18,6 @@ import (
1718
"github.com/docker/cli/internal/registry"
1819
"github.com/docker/cli/internal/tui"
1920
"github.com/morikuni/aec"
20-
"github.com/sirupsen/logrus"
21-
2221
"github.com/pkg/browser"
2322
)
2423

@@ -83,29 +82,29 @@ var ErrDeviceLoginStartFail = errors.New("failed to start device code flow login
8382
// The retrieved tokens are stored in the credentials store (under a separate
8483
// key), and the refresh token is concatenated with the client ID.
8584
func (m *OAuthManager) LoginDevice(ctx context.Context, w io.Writer) (*types.AuthConfig, error) {
85+
ctx, cancel := context.WithCancel(ctx)
86+
defer cancel()
87+
8688
state, err := m.api.GetDeviceCode(ctx, m.audience)
8789
if err != nil {
88-
logrus.Debugf("failed to start device code login: %v", err)
89-
return nil, ErrDeviceLoginStartFail
90+
return nil, fmt.Errorf("%w: %w", ErrDeviceLoginStartFail, err)
9091
}
9192

9293
if state.UserCode == "" {
93-
logrus.Debugf("failed to start device code login: missing user code")
94-
return nil, ErrDeviceLoginStartFail
94+
return nil, fmt.Errorf("%w: missing user code", ErrDeviceLoginStartFail)
9595
}
9696

97-
_, _ = fmt.Fprintln(w, aec.Bold.Apply("\nUSING WEB-BASED LOGIN"))
98-
9997
var out tui.Output
10098
switch stream := w.(type) {
10199
case *streams.Out:
102100
out = tui.NewOutput(stream)
103101
default:
104102
out = tui.NewOutput(streams.NewOut(w))
105103
}
106-
out.PrintNote("To sign in with credentials on the command line, use 'docker login -u <username>'\n")
107-
_, _ = fmt.Fprintf(w, "\nYour one-time device confirmation code is: "+aec.Bold.Apply("%s\n"), state.UserCode)
108-
_, _ = 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])
104+
out.PrintNote("Using web-based login. To sign in with credentials on the command line, use 'docker login -u <username>'\n")
105+
_, _ = fmt.Fprintln(w, "Your one-time device confirmation code is:", aec.Bold.Apply(state.UserCode))
106+
_, _ = fmt.Fprintln(w, "Open this URL to authenticate:", aec.Underline.Apply(state.VerificationURI))
107+
_, _ = fmt.Fprintln(w, "\n"+aec.Bold.Apply("Press ENTER")+" to open the URL in your browser, or "+aec.Bold.Apply("Ctrl+C")+" to cancel.")
109108

110109
tokenResChan := make(chan api.TokenResponse)
111110
waitForTokenErrChan := make(chan error)
@@ -120,11 +119,25 @@ func (m *OAuthManager) LoginDevice(ctx context.Context, w io.Writer) (*types.Aut
120119

121120
go func() {
122121
reader := bufio.NewReader(os.Stdin)
123-
_, _ = reader.ReadString('\n')
124-
_ = m.openBrowser(state.VerificationURI)
122+
if _, err := reader.ReadString('\n'); err != nil || ctx.Err() != nil {
123+
return
124+
}
125+
err := m.openBrowser(state.VerificationURI)
126+
if ctx.Err() != nil {
127+
return
128+
}
129+
if err != nil {
130+
if errors.Is(err, exec.ErrNotFound) {
131+
_, _ = fmt.Fprintln(w, "No browser found. Open the URL above manually to continue.")
132+
} else {
133+
_, _ = fmt.Fprintln(w, "Failed to open browser:", err)
134+
_, _ = fmt.Fprintln(w, "Open the URL above manually to continue.")
135+
}
136+
return
137+
}
138+
_, _ = fmt.Fprintln(w, "\nOpening browser…")
125139
}()
126140

127-
_, _ = fmt.Fprint(w, "\nWaiting for authentication in the browser…\n")
128141
var tokenRes api.TokenResponse
129142
select {
130143
case <-ctx.Done():

0 commit comments

Comments
 (0)