github.com/panekj/cli@v0.0.0-20230304125325-467dd2f3797e/cli/command/registry.go (about)

     1  package command
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"encoding/base64"
     7  	"encoding/json"
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"runtime"
    12  	"strings"
    13  
    14  	configtypes "github.com/docker/cli/cli/config/types"
    15  	"github.com/docker/cli/cli/streams"
    16  	"github.com/docker/distribution/reference"
    17  	"github.com/docker/docker/api/types"
    18  	registrytypes "github.com/docker/docker/api/types/registry"
    19  	"github.com/docker/docker/registry"
    20  	"github.com/moby/term"
    21  	"github.com/pkg/errors"
    22  )
    23  
    24  // ElectAuthServer returns the default registry to use
    25  // Deprecated: use registry.IndexServer instead
    26  func ElectAuthServer(_ context.Context, _ Cli) string {
    27  	return registry.IndexServer
    28  }
    29  
    30  // EncodeAuthToBase64 serializes the auth configuration as JSON base64 payload
    31  func EncodeAuthToBase64(authConfig types.AuthConfig) (string, error) {
    32  	buf, err := json.Marshal(authConfig)
    33  	if err != nil {
    34  		return "", err
    35  	}
    36  	return base64.URLEncoding.EncodeToString(buf), nil
    37  }
    38  
    39  // RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
    40  // for the given command.
    41  func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInfo, cmdName string) types.RequestPrivilegeFunc {
    42  	return func() (string, error) {
    43  		fmt.Fprintf(cli.Out(), "\nPlease login prior to %s:\n", cmdName)
    44  		indexServer := registry.GetAuthConfigKey(index)
    45  		isDefaultRegistry := indexServer == registry.IndexServer
    46  		authConfig, err := GetDefaultAuthConfig(cli, true, indexServer, isDefaultRegistry)
    47  		if err != nil {
    48  			fmt.Fprintf(cli.Err(), "Unable to retrieve stored credentials for %s, error: %s.\n", indexServer, err)
    49  		}
    50  		err = ConfigureAuth(cli, "", "", &authConfig, isDefaultRegistry)
    51  		if err != nil {
    52  			return "", err
    53  		}
    54  		return EncodeAuthToBase64(authConfig)
    55  	}
    56  }
    57  
    58  // ResolveAuthConfig is like registry.ResolveAuthConfig, but if using the
    59  // default index, it uses the default index name for the daemon's platform,
    60  // not the client's platform.
    61  func ResolveAuthConfig(_ context.Context, cli Cli, index *registrytypes.IndexInfo) types.AuthConfig {
    62  	configKey := index.Name
    63  	if index.Official {
    64  		configKey = registry.IndexServer
    65  	}
    66  
    67  	a, _ := cli.ConfigFile().GetAuthConfig(configKey)
    68  	return types.AuthConfig(a)
    69  }
    70  
    71  // GetDefaultAuthConfig gets the default auth config given a serverAddress
    72  // If credentials for given serverAddress exists in the credential store, the configuration will be populated with values in it
    73  func GetDefaultAuthConfig(cli Cli, checkCredStore bool, serverAddress string, isDefaultRegistry bool) (types.AuthConfig, error) {
    74  	if !isDefaultRegistry {
    75  		serverAddress = registry.ConvertToHostname(serverAddress)
    76  	}
    77  	authconfig := configtypes.AuthConfig{}
    78  	var err error
    79  	if checkCredStore {
    80  		authconfig, err = cli.ConfigFile().GetAuthConfig(serverAddress)
    81  		if err != nil {
    82  			return types.AuthConfig{
    83  				ServerAddress: serverAddress,
    84  			}, err
    85  		}
    86  	}
    87  	authconfig.ServerAddress = serverAddress
    88  	authconfig.IdentityToken = ""
    89  	res := types.AuthConfig(authconfig)
    90  	return res, nil
    91  }
    92  
    93  // ConfigureAuth handles prompting of user's username and password if needed
    94  func ConfigureAuth(cli Cli, flUser, flPassword string, authconfig *types.AuthConfig, isDefaultRegistry bool) error {
    95  	// On Windows, force the use of the regular OS stdin stream. Fixes #14336/#14210
    96  	if runtime.GOOS == "windows" {
    97  		cli.SetIn(streams.NewIn(os.Stdin))
    98  	}
    99  
   100  	// Some links documenting this:
   101  	// - https://code.google.com/archive/p/mintty/issues/56
   102  	// - https://github.com/docker/docker/issues/15272
   103  	// - https://mintty.github.io/ (compatibility)
   104  	// Linux will hit this if you attempt `cat | docker login`, and Windows
   105  	// will hit this if you attempt docker login from mintty where stdin
   106  	// is a pipe, not a character based console.
   107  	if flPassword == "" && !cli.In().IsTerminal() {
   108  		return errors.Errorf("Error: Cannot perform an interactive login from a non TTY device")
   109  	}
   110  
   111  	authconfig.Username = strings.TrimSpace(authconfig.Username)
   112  
   113  	if flUser = strings.TrimSpace(flUser); flUser == "" {
   114  		if isDefaultRegistry {
   115  			// if this is a default registry (docker hub), then display the following message.
   116  			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.")
   117  		}
   118  		promptWithDefault(cli.Out(), "Username", authconfig.Username)
   119  		flUser = readInput(cli.In(), cli.Out())
   120  		flUser = strings.TrimSpace(flUser)
   121  		if flUser == "" {
   122  			flUser = authconfig.Username
   123  		}
   124  	}
   125  	if flUser == "" {
   126  		return errors.Errorf("Error: Non-null Username Required")
   127  	}
   128  	if flPassword == "" {
   129  		oldState, err := term.SaveState(cli.In().FD())
   130  		if err != nil {
   131  			return err
   132  		}
   133  		fmt.Fprintf(cli.Out(), "Password: ")
   134  		term.DisableEcho(cli.In().FD(), oldState)
   135  
   136  		flPassword = readInput(cli.In(), cli.Out())
   137  		fmt.Fprint(cli.Out(), "\n")
   138  
   139  		term.RestoreTerminal(cli.In().FD(), oldState)
   140  		if flPassword == "" {
   141  			return errors.Errorf("Error: Password Required")
   142  		}
   143  	}
   144  
   145  	authconfig.Username = flUser
   146  	authconfig.Password = flPassword
   147  
   148  	return nil
   149  }
   150  
   151  func readInput(in io.Reader, out io.Writer) string {
   152  	reader := bufio.NewReader(in)
   153  	line, _, err := reader.ReadLine()
   154  	if err != nil {
   155  		fmt.Fprintln(out, err.Error())
   156  		os.Exit(1)
   157  	}
   158  	return string(line)
   159  }
   160  
   161  func promptWithDefault(out io.Writer, prompt string, configDefault string) {
   162  	if configDefault == "" {
   163  		fmt.Fprintf(out, "%s: ", prompt)
   164  	} else {
   165  		fmt.Fprintf(out, "%s (%s): ", prompt, configDefault)
   166  	}
   167  }
   168  
   169  // RetrieveAuthTokenFromImage retrieves an encoded auth token given a complete image
   170  func RetrieveAuthTokenFromImage(ctx context.Context, cli Cli, image string) (string, error) {
   171  	// Retrieve encoded auth token from the image reference
   172  	authConfig, err := resolveAuthConfigFromImage(ctx, cli, image)
   173  	if err != nil {
   174  		return "", err
   175  	}
   176  	encodedAuth, err := EncodeAuthToBase64(authConfig)
   177  	if err != nil {
   178  		return "", err
   179  	}
   180  	return encodedAuth, nil
   181  }
   182  
   183  // resolveAuthConfigFromImage retrieves that AuthConfig using the image string
   184  func resolveAuthConfigFromImage(ctx context.Context, cli Cli, image string) (types.AuthConfig, error) {
   185  	registryRef, err := reference.ParseNormalizedNamed(image)
   186  	if err != nil {
   187  		return types.AuthConfig{}, err
   188  	}
   189  	repoInfo, err := registry.ParseRepositoryInfo(registryRef)
   190  	if err != nil {
   191  		return types.AuthConfig{}, err
   192  	}
   193  	return ResolveAuthConfig(ctx, cli, repoInfo.Index), nil
   194  }