mirror of https://github.com/docker/cli.git
Compare commits
14 Commits
d2b87a0a3b
...
0ab0eca8bd
Author | SHA1 | Date |
---|---|---|
Sebastiaan van Stijn | 0ab0eca8bd | |
Sebastiaan van Stijn | abb8e9b78a | |
Laura Brehm | 7029147458 | |
Sebastiaan van Stijn | 6b9083776f | |
Sebastiaan van Stijn | fb61156b05 | |
Sebastiaan van Stijn | 4b7a1e4613 | |
Sebastiaan van Stijn | 378a3d7d36 | |
Sebastiaan van Stijn | 3d8b49523d | |
Sebastiaan van Stijn | a21a5f4243 | |
Sebastiaan van Stijn | eda78e9cdc | |
Sebastiaan van Stijn | 581cf36bd4 | |
Sebastiaan van Stijn | a55cfe5f82 | |
Sebastiaan van Stijn | 3a8485085d | |
Sebastiaan van Stijn | 87acf77aef |
|
@ -18,20 +18,24 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
const patSuggest = "You can log in with your password or a Personal Access " +
|
const (
|
||||||
"Token (PAT). Using a limited-scope PAT grants better security and is required " +
|
registerSuggest = "Log in with your Docker ID or email address to push and pull images from Docker Hub. " +
|
||||||
"for organizations using SSO. Learn more at https://docs.docker.com/go/access-tokens/"
|
"If you don't have a Docker ID, head over to https://hub.docker.com/ to create one."
|
||||||
|
patSuggest = "You can log in with your password or a Personal Access " +
|
||||||
|
"Token (PAT). Using a limited-scope PAT grants better security and is required " +
|
||||||
|
"for organizations using SSO. Learn more at https://docs.docker.com/go/access-tokens/"
|
||||||
|
)
|
||||||
|
|
||||||
// RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
|
// RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
|
||||||
// for the given command.
|
// for the given command.
|
||||||
func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInfo, cmdName string) registrytypes.RequestAuthConfig {
|
func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInfo, cmdName string) registrytypes.RequestAuthConfig {
|
||||||
return func(ctx context.Context) (string, error) {
|
return func(ctx context.Context) (string, error) {
|
||||||
fmt.Fprintf(cli.Out(), "\nLogin prior to %s:\n", cmdName)
|
_, _ = fmt.Fprintf(cli.Out(), "\nLogin prior to %s:\n", cmdName)
|
||||||
indexServer := registry.GetAuthConfigKey(index)
|
indexServer := registry.GetAuthConfigKey(index)
|
||||||
isDefaultRegistry := indexServer == registry.IndexServer
|
isDefaultRegistry := indexServer == registry.IndexServer
|
||||||
authConfig, err := GetDefaultAuthConfig(cli.ConfigFile(), true, indexServer, isDefaultRegistry)
|
authConfig, err := GetDefaultAuthConfig(cli.ConfigFile(), true, indexServer, isDefaultRegistry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(cli.Err(), "Unable to retrieve stored credentials for %s, error: %s.\n", indexServer, err)
|
_, _ = fmt.Fprintf(cli.Err(), "Unable to retrieve stored credentials for %s, error: %s.\n", indexServer, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
|
@ -111,7 +115,7 @@ func ConfigureAuth(ctx context.Context, cli Cli, flUser, flPassword string, auth
|
||||||
// If defaultUsername is not empty, the username prompt includes that username
|
// If defaultUsername is not empty, the username prompt includes that username
|
||||||
// and the user can hit enter without inputting a username to use that default
|
// and the user can hit enter without inputting a username to use that default
|
||||||
// username.
|
// username.
|
||||||
func PromptUserForCredentials(ctx context.Context, cli Cli, argUser, argPassword, defaultUsername, serverAddress string) (authConfig registrytypes.AuthConfig, err error) {
|
func PromptUserForCredentials(ctx context.Context, cli Cli, argUser, argPassword, defaultUsername, serverAddress string) (registrytypes.AuthConfig, error) {
|
||||||
// On Windows, force the use of the regular OS stdin stream.
|
// On Windows, force the use of the regular OS stdin stream.
|
||||||
//
|
//
|
||||||
// See:
|
// See:
|
||||||
|
@ -124,57 +128,71 @@ func PromptUserForCredentials(ctx context.Context, cli Cli, argUser, argPassword
|
||||||
cli.SetIn(streams.NewIn(os.Stdin))
|
cli.SetIn(streams.NewIn(os.Stdin))
|
||||||
}
|
}
|
||||||
|
|
||||||
isDefaultRegistry := serverAddress == registry.IndexServer
|
argUser = strings.TrimSpace(argUser)
|
||||||
defaultUsername = strings.TrimSpace(defaultUsername)
|
if argUser == "" {
|
||||||
|
if serverAddress == registry.IndexServer {
|
||||||
if argUser = strings.TrimSpace(argUser); argUser == "" {
|
// When signing in to the default (Docker Hub) registry, we display
|
||||||
if isDefaultRegistry {
|
// hints for creating an account, and (if hints are enabled), using
|
||||||
// if this is a default registry (docker hub), then display the following message.
|
// a token instead of a password.
|
||||||
fmt.Fprintln(cli.Out(), "Log in with your Docker ID or email address 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.")
|
_, _ = fmt.Fprintln(cli.Out(), registerSuggest)
|
||||||
if hints.Enabled() {
|
if hints.Enabled() {
|
||||||
fmt.Fprintln(cli.Out(), patSuggest)
|
_, _ = fmt.Fprintln(cli.Out(), patSuggest)
|
||||||
fmt.Fprintln(cli.Out())
|
_, _ = fmt.Fprintln(cli.Out())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var prompt string
|
var prompt string
|
||||||
|
defaultUsername = strings.TrimSpace(defaultUsername)
|
||||||
if defaultUsername == "" {
|
if defaultUsername == "" {
|
||||||
prompt = "Username: "
|
prompt = "Username: "
|
||||||
} else {
|
} else {
|
||||||
prompt = fmt.Sprintf("Username (%s): ", defaultUsername)
|
prompt = fmt.Sprintf("Username (%s): ", defaultUsername)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
argUser, err = PromptForInput(ctx, cli.In(), cli.Out(), prompt)
|
argUser, err = PromptForInput(ctx, cli.In(), cli.Out(), prompt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return authConfig, err
|
return registrytypes.AuthConfig{}, err
|
||||||
}
|
}
|
||||||
if argUser == "" {
|
if argUser == "" {
|
||||||
argUser = defaultUsername
|
argUser = defaultUsername
|
||||||
}
|
}
|
||||||
|
if argUser == "" {
|
||||||
|
return registrytypes.AuthConfig{}, errors.Errorf("Error: Non-null Username Required")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if argUser == "" {
|
|
||||||
return authConfig, errors.Errorf("Error: Non-null Username Required")
|
argPassword = strings.TrimSpace(argPassword)
|
||||||
}
|
|
||||||
if argPassword == "" {
|
if argPassword == "" {
|
||||||
restoreInput, err := DisableInputEcho(cli.In())
|
restoreInput, err := DisableInputEcho(cli.In())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return authConfig, err
|
return registrytypes.AuthConfig{}, err
|
||||||
}
|
}
|
||||||
defer restoreInput()
|
defer func() {
|
||||||
|
if err := restoreInput(); err != nil {
|
||||||
|
// TODO(thaJeztah): we should consider printing instructions how
|
||||||
|
// to restore this manually (other than restarting the shell).
|
||||||
|
// e.g., 'run stty echo' when in a Linux or macOS shell, but
|
||||||
|
// PowerShell and CMD.exe may need different instructions.
|
||||||
|
_, _ = fmt.Fprintln(cli.Err(), "Error: failed to restore terminal state to echo input:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
argPassword, err = PromptForInput(ctx, cli.In(), cli.Out(), "Password: ")
|
argPassword, err = PromptForInput(ctx, cli.In(), cli.Out(), "Password: ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return authConfig, err
|
return registrytypes.AuthConfig{}, err
|
||||||
}
|
}
|
||||||
fmt.Fprint(cli.Out(), "\n")
|
_, _ = fmt.Fprintln(cli.Out())
|
||||||
if argPassword == "" {
|
if argPassword == "" {
|
||||||
return authConfig, errors.Errorf("Error: Password Required")
|
return registrytypes.AuthConfig{}, errors.Errorf("Error: Password Required")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
authConfig.Username = argUser
|
return registrytypes.AuthConfig{
|
||||||
authConfig.Password = argPassword
|
Username: argUser,
|
||||||
authConfig.ServerAddress = serverAddress
|
Password: argPassword,
|
||||||
return authConfig, nil
|
ServerAddress: serverAddress,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RetrieveAuthTokenFromImage retrieves an encoded auth token given a complete
|
// RetrieveAuthTokenFromImage retrieves an encoded auth token given a complete
|
||||||
|
|
|
@ -58,9 +58,9 @@ func NewLoginCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyloginOptions(dockerCli command.Cli, opts *loginOptions) error {
|
func verifyLoginOptions(dockerCli command.Cli, opts *loginOptions) error {
|
||||||
if opts.password != "" {
|
if opts.password != "" {
|
||||||
fmt.Fprintln(dockerCli.Err(), "WARNING! Using --password via the CLI is insecure. Use --password-stdin.")
|
_, _ = fmt.Fprintln(dockerCli.Err(), "WARNING! Using --password via the CLI is insecure. Use --password-stdin.")
|
||||||
if opts.passwordStdin {
|
if opts.passwordStdin {
|
||||||
return errors.New("--password and --password-stdin are mutually exclusive")
|
return errors.New("--password and --password-stdin are mutually exclusive")
|
||||||
}
|
}
|
||||||
|
@ -83,7 +83,7 @@ func verifyloginOptions(dockerCli command.Cli, opts *loginOptions) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func runLogin(ctx context.Context, dockerCli command.Cli, opts loginOptions) error {
|
func runLogin(ctx context.Context, dockerCli command.Cli, opts loginOptions) error {
|
||||||
if err := verifyloginOptions(dockerCli, &opts); err != nil {
|
if err := verifyLoginOptions(dockerCli, &opts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
|
@ -174,7 +174,7 @@ func loginUser(ctx context.Context, dockerCli command.Cli, opts loginOptions, de
|
||||||
if !errors.Is(err, manager.ErrDeviceLoginStartFail) {
|
if !errors.Is(err, manager.ErrDeviceLoginStartFail) {
|
||||||
return response, err
|
return response, err
|
||||||
}
|
}
|
||||||
fmt.Fprint(dockerCli.Err(), "Failed to start web-based login - falling back to command line login...\n\n")
|
_, _ = 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)
|
return loginWithUsernameAndPassword(ctx, dockerCli, opts, defaultUsername, serverAddress)
|
||||||
|
|
|
@ -199,7 +199,7 @@ func PruneFilters(dockerCli Cli, pruneFilters filters.Args) filters.Args {
|
||||||
// AddPlatformFlag adds `platform` to a set of flags for API version 1.32 and later.
|
// AddPlatformFlag adds `platform` to a set of flags for API version 1.32 and later.
|
||||||
func AddPlatformFlag(flags *pflag.FlagSet, target *string) {
|
func AddPlatformFlag(flags *pflag.FlagSet, target *string) {
|
||||||
flags.StringVar(target, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable")
|
flags.StringVar(target, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable")
|
||||||
flags.SetAnnotation("platform", "version", []string{"1.32"})
|
_ = flags.SetAnnotation("platform", "version", []string{"1.32"})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateOutputPath validates the output paths of the `export` and `save` commands.
|
// ValidateOutputPath validates the output paths of the `export` and `save` commands.
|
||||||
|
|
|
@ -5,7 +5,9 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Enabled returns whether cli hints are enabled or not
|
// Enabled returns whether cli hints are enabled or not. Hints are enabled by
|
||||||
|
// default, but can be disabled through the "DOCKER_CLI_HINTS" environment
|
||||||
|
// variable.
|
||||||
func Enabled() bool {
|
func Enabled() bool {
|
||||||
if v := os.Getenv("DOCKER_CLI_HINTS"); v != "" {
|
if v := os.Getenv("DOCKER_CLI_HINTS"); v != "" {
|
||||||
enabled, err := strconv.ParseBool(v)
|
enabled, err := strconv.ParseBool(v)
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
package hints
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEnabled(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
doc string
|
||||||
|
env string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
doc: "default",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "DOCKER_CLI_HINTS=1",
|
||||||
|
env: "1",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "DOCKER_CLI_HINTS=true",
|
||||||
|
env: "true",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "DOCKER_CLI_HINTS=0",
|
||||||
|
env: "0",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "DOCKER_CLI_HINTS=false",
|
||||||
|
env: "false",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "DOCKER_CLI_HINTS=not-a-bool",
|
||||||
|
env: "not-a-bool",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.doc, func(t *testing.T) {
|
||||||
|
t.Setenv("DOCKER_CLI_HINTS", tc.env)
|
||||||
|
assert.Equal(t, Enabled(), tc.expected)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue