diff --git a/cli/command/registry/login.go b/cli/command/registry/login.go index 1a40213eac..a10a58f071 100644 --- a/cli/command/registry/login.go +++ b/cli/command/registry/login.go @@ -165,10 +165,16 @@ func loginWithStoredCredentials(ctx context.Context, dockerCli command.Cli, auth func loginUser(ctx context.Context, dockerCli command.Cli, opts loginOptions, defaultUsername, serverAddress string) (*registrytypes.AuthenticateOKBody, error) { // If we're logging into the index server and the user didn't provide a username or password, use the device flow if serverAddress == registry.IndexServer && opts.user == "" && opts.password == "" { - return loginWithDeviceCodeFlow(ctx, dockerCli) - } else { - return loginWithUsernameAndPassword(ctx, dockerCli, opts, defaultUsername, serverAddress) + response, err := loginWithDeviceCodeFlow(ctx, dockerCli) + // if the error represents a failure to initiate the device-code flow, + // then we fallback to regular cli credentials login + if !errors.Is(err, manager.ErrDeviceLoginStartFail) { + return response, err + } + fmt.Fprint(dockerCli.Err(), "Failed to start web-based login - falling back to command line login...\n\n") } + + return loginWithUsernameAndPassword(ctx, dockerCli, opts, defaultUsername, serverAddress) } func loginWithUsernameAndPassword(ctx context.Context, dockerCli command.Cli, opts loginOptions, defaultUsername, serverAddress string) (*registrytypes.AuthenticateOKBody, error) { diff --git a/cli/internal/oauth/manager/manager.go b/cli/internal/oauth/manager/manager.go index 2dce3a4647..a39612a2a4 100644 --- a/cli/internal/oauth/manager/manager.go +++ b/cli/internal/oauth/manager/manager.go @@ -15,6 +15,7 @@ import ( "github.com/docker/cli/cli/internal/oauth/api" "github.com/docker/docker/registry" "github.com/morikuni/aec" + "github.com/sirupsen/logrus" "github.com/pkg/browser" ) @@ -70,6 +71,8 @@ func New(options OAuthManagerOptions) *OAuthManager { } } +var ErrDeviceLoginStartFail = errors.New("failed to start device code flow login") + // LoginDevice launches the device authentication flow with the tenant, // printing instructions to the provided writer and attempting to open the // browser for the user to authenticate. @@ -80,11 +83,13 @@ func New(options OAuthManagerOptions) *OAuthManager { func (m *OAuthManager) LoginDevice(ctx context.Context, w io.Writer) (*types.AuthConfig, error) { state, err := m.api.GetDeviceCode(ctx, m.audience) if err != nil { - return nil, fmt.Errorf("failed to get device code: %w", err) + logrus.Debugf("failed to start device code login: %v", err) + return nil, ErrDeviceLoginStartFail } if state.UserCode == "" { - return nil, errors.New("no user code returned") + logrus.Debugf("failed to start device code login: missing user code") + return nil, ErrDeviceLoginStartFail } _, _ = fmt.Fprintln(w, aec.Bold.Apply("\nUSING WEB BASED LOGIN"))