mirror of https://github.com/docker/cli.git
Merge pull request #227 from dnephin/expose-config
expose config credentials without needing the Cli
This commit is contained in:
commit
74af31be7f
|
@ -1,7 +1,6 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
|
@ -10,11 +9,9 @@ import (
|
|||
"github.com/docker/cli/cli"
|
||||
cliconfig "github.com/docker/cli/cli/config"
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/cli/config/credentials"
|
||||
cliflags "github.com/docker/cli/cli/flags"
|
||||
dopts "github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/go-connections/sockets"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
|
@ -39,7 +36,7 @@ type Cli interface {
|
|||
In() *InStream
|
||||
SetIn(in *InStream)
|
||||
ConfigFile() *configfile.ConfigFile
|
||||
CredentialsStore(serverAddress string) credentials.Store
|
||||
ServerInfo() ServerInfo
|
||||
}
|
||||
|
||||
// DockerCli is an instance the docker command line client.
|
||||
|
@ -104,59 +101,10 @@ func (cli *DockerCli) ServerInfo() ServerInfo {
|
|||
return cli.server
|
||||
}
|
||||
|
||||
// GetAllCredentials returns all of the credentials stored in all of the
|
||||
// configured credential stores.
|
||||
func (cli *DockerCli) GetAllCredentials() (map[string]types.AuthConfig, error) {
|
||||
auths := make(map[string]types.AuthConfig)
|
||||
for registry := range cli.configFile.CredentialHelpers {
|
||||
helper := cli.CredentialsStore(registry)
|
||||
newAuths, err := helper.GetAll()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addAll(auths, newAuths)
|
||||
}
|
||||
defaultStore := cli.CredentialsStore("")
|
||||
newAuths, err := defaultStore.GetAll()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addAll(auths, newAuths)
|
||||
return auths, nil
|
||||
}
|
||||
|
||||
func addAll(to, from map[string]types.AuthConfig) {
|
||||
for reg, ac := range from {
|
||||
to[reg] = ac
|
||||
}
|
||||
}
|
||||
|
||||
// CredentialsStore returns a new credentials store based
|
||||
// on the settings provided in the configuration file. Empty string returns
|
||||
// the default credential store.
|
||||
func (cli *DockerCli) CredentialsStore(serverAddress string) credentials.Store {
|
||||
if helper := getConfiguredCredentialStore(cli.configFile, serverAddress); helper != "" {
|
||||
return credentials.NewNativeStore(cli.configFile, helper)
|
||||
}
|
||||
return credentials.NewFileStore(cli.configFile)
|
||||
}
|
||||
|
||||
// getConfiguredCredentialStore returns the credential helper configured for the
|
||||
// given registry, the default credsStore, or the empty string if neither are
|
||||
// configured.
|
||||
func getConfiguredCredentialStore(c *configfile.ConfigFile, serverAddress string) string {
|
||||
if c.CredentialHelpers != nil && serverAddress != "" {
|
||||
if helper, exists := c.CredentialHelpers[serverAddress]; exists {
|
||||
return helper
|
||||
}
|
||||
}
|
||||
return c.CredentialsStore
|
||||
}
|
||||
|
||||
// Initialize the dockerCli runs initialization that must happen after command
|
||||
// line flags are parsed.
|
||||
func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
|
||||
cli.configFile = LoadDefaultConfigFile(cli.err)
|
||||
cli.configFile = cliconfig.LoadDefaultConfigFile(cli.err)
|
||||
|
||||
var err error
|
||||
cli.client, err = NewAPIClientFromFlags(opts.Common, cli.configFile)
|
||||
|
@ -213,19 +161,6 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer) *DockerCli {
|
|||
return &DockerCli{in: NewInStream(in), out: NewOutStream(out), err: err}
|
||||
}
|
||||
|
||||
// LoadDefaultConfigFile attempts to load the default config file and returns
|
||||
// an initialized ConfigFile struct if none is found.
|
||||
func LoadDefaultConfigFile(err io.Writer) *configfile.ConfigFile {
|
||||
configFile, e := cliconfig.Load(cliconfig.Dir())
|
||||
if e != nil {
|
||||
fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e)
|
||||
}
|
||||
if !configFile.ContainsAuth() {
|
||||
credentials.DetectDefaultStore(configFile)
|
||||
}
|
||||
return configFile
|
||||
}
|
||||
|
||||
// NewAPIClientFromFlags creates a new APIClient from command line flags
|
||||
func NewAPIClientFromFlags(opts *cliflags.CommonOptions, configFile *configfile.ConfigFile) (client.APIClient, error) {
|
||||
host, err := getServerHost(opts.Hosts, opts.TLSOptions)
|
||||
|
|
|
@ -78,7 +78,7 @@ func (o buildOptions) contextFromStdin() bool {
|
|||
}
|
||||
|
||||
// NewBuildCommand creates a new `docker build` command
|
||||
func NewBuildCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func NewBuildCommand(dockerCli command.Cli) *cobra.Command {
|
||||
ulimits := make(map[string]*units.Ulimit)
|
||||
options := buildOptions{
|
||||
tags: opts.NewListOpts(validateTag),
|
||||
|
@ -159,7 +159,7 @@ func (out *lastProgressOutput) WriteProgress(prog progress.Progress) error {
|
|||
}
|
||||
|
||||
// nolint: gocyclo
|
||||
func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
|
||||
func runBuild(dockerCli command.Cli, options buildOptions) error {
|
||||
var (
|
||||
buildCtx io.ReadCloser
|
||||
dockerfileCtx io.ReadCloser
|
||||
|
@ -336,7 +336,8 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
|
|||
body = buildCtx
|
||||
}
|
||||
|
||||
authConfigs, _ := dockerCli.GetAllCredentials()
|
||||
configFile := dockerCli.ConfigFile()
|
||||
authConfigs, _ := configFile.GetAllCredentials()
|
||||
buildOptions := types.ImageBuildOptions{
|
||||
Memory: options.memory.Value(),
|
||||
MemorySwap: options.memorySwap.Value(),
|
||||
|
@ -356,7 +357,7 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
|
|||
Dockerfile: relDockerfile,
|
||||
ShmSize: options.shmSize.Value(),
|
||||
Ulimits: options.ulimits.GetList(),
|
||||
BuildArgs: dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), options.buildArgs.GetAll()),
|
||||
BuildArgs: configFile.ParseProxyConfig(dockerCli.Client().DaemonHost(), options.buildArgs.GetAll()),
|
||||
AuthConfigs: authConfigs,
|
||||
Labels: opts.ConvertKVStringsToMap(options.labels.GetAll()),
|
||||
CacheFrom: options.cacheFrom,
|
||||
|
|
|
@ -26,11 +26,11 @@ import (
|
|||
|
||||
const clientSessionRemote = "client-session"
|
||||
|
||||
func isSessionSupported(dockerCli *command.DockerCli) bool {
|
||||
func isSessionSupported(dockerCli command.Cli) bool {
|
||||
return dockerCli.ServerInfo().HasExperimental && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.31")
|
||||
}
|
||||
|
||||
func trySession(dockerCli *command.DockerCli, contextDir string) (*session.Session, error) {
|
||||
func trySession(dockerCli command.Cli, contextDir string) (*session.Session, error) {
|
||||
var s *session.Session
|
||||
if isSessionSupported(dockerCli) {
|
||||
sharedKey, err := getBuildSharedKey(contextDir)
|
||||
|
|
|
@ -8,8 +8,7 @@ import (
|
|||
)
|
||||
|
||||
// NewImageCommand returns a cobra command for `image` subcommands
|
||||
// nolint: interfacer
|
||||
func NewImageCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func NewImageCommand(dockerCli command.Cli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "image",
|
||||
Short: "Manage images",
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/cli/internal/test"
|
||||
"github.com/docker/docker/pkg/testutil"
|
||||
"github.com/docker/docker/pkg/testutil/golden"
|
||||
|
@ -41,7 +42,9 @@ func TestNewPullCommandErrors(t *testing.T) {
|
|||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := NewPullCommand(test.NewFakeCli(&fakeClient{}, buf))
|
||||
cli := test.NewFakeCli(&fakeClient{}, buf)
|
||||
cli.SetConfigfile(configfile.New("filename"))
|
||||
cmd := NewPullCommand(cli)
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
cmd.SetArgs(tc.args)
|
||||
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
|
||||
|
@ -64,7 +67,9 @@ func TestNewPullCommandSuccess(t *testing.T) {
|
|||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := NewPullCommand(test.NewFakeCli(&fakeClient{}, buf))
|
||||
cli := test.NewFakeCli(&fakeClient{}, buf)
|
||||
cli.SetConfigfile(configfile.New("filename"))
|
||||
cmd := NewPullCommand(cli)
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
cmd.SetArgs(tc.args)
|
||||
err := cmd.Execute()
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/cli/internal/test"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/testutil"
|
||||
|
@ -47,7 +48,9 @@ func TestNewPushCommandErrors(t *testing.T) {
|
|||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := NewPushCommand(test.NewFakeCli(&fakeClient{imagePushFunc: tc.imagePushFunc}, buf))
|
||||
cli := test.NewFakeCli(&fakeClient{imagePushFunc: tc.imagePushFunc}, buf)
|
||||
cli.SetConfigfile(configfile.New("filename"))
|
||||
cmd := NewPushCommand(cli)
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
cmd.SetArgs(tc.args)
|
||||
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
|
||||
|
@ -66,11 +69,13 @@ func TestNewPushCommandSuccess(t *testing.T) {
|
|||
}
|
||||
for _, tc := range testCases {
|
||||
buf := new(bytes.Buffer)
|
||||
cmd := NewPushCommand(test.NewFakeCli(&fakeClient{
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
imagePushFunc: func(ref string, options types.ImagePushOptions) (io.ReadCloser, error) {
|
||||
return ioutil.NopCloser(strings.NewReader("")), nil
|
||||
},
|
||||
}, buf))
|
||||
}, buf)
|
||||
cli.SetConfigfile(configfile.New("filename"))
|
||||
cmd := NewPushCommand(cli)
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
cmd.SetArgs(tc.args)
|
||||
assert.NoError(t, cmd.Execute())
|
||||
|
|
|
@ -70,7 +70,7 @@ func ResolveAuthConfig(ctx context.Context, cli Cli, index *registrytypes.IndexI
|
|||
configKey = ElectAuthServer(ctx, cli)
|
||||
}
|
||||
|
||||
a, _ := cli.CredentialsStore(configKey).Get(configKey)
|
||||
a, _ := cli.ConfigFile().GetAuthConfig(configKey)
|
||||
return a
|
||||
}
|
||||
|
||||
|
@ -85,7 +85,7 @@ func ConfigureAuth(cli Cli, flUser, flPassword, serverAddress string, isDefaultR
|
|||
serverAddress = registry.ConvertToHostname(serverAddress)
|
||||
}
|
||||
|
||||
authconfig, err := cli.CredentialsStore(serverAddress).Get(serverAddress)
|
||||
authconfig, err := cli.ConfigFile().GetAuthConfig(serverAddress)
|
||||
if err != nil {
|
||||
return authconfig, err
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ func runLogin(dockerCli command.Cli, opts loginOptions) error {
|
|||
authConfig.Password = ""
|
||||
authConfig.IdentityToken = response.IdentityToken
|
||||
}
|
||||
if err := dockerCli.CredentialsStore(serverAddress).Store(authConfig); err != nil {
|
||||
if err := dockerCli.ConfigFile().GetCredentialsStore(serverAddress).Store(authConfig); err != nil {
|
||||
return errors.Errorf("Error saving credentials: %v", err)
|
||||
}
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ func runLogout(dockerCli command.Cli, serverAddress string) error {
|
|||
|
||||
fmt.Fprintf(dockerCli.Out(), "Removing login credentials for %s\n", hostnameAddress)
|
||||
for _, r := range regsToLogout {
|
||||
if err := dockerCli.CredentialsStore(r).Erase(r); err != nil {
|
||||
if err := dockerCli.ConfigFile().GetCredentialsStore(r).Erase(r); err != nil {
|
||||
fmt.Fprintf(dockerCli.Err(), "WARNING: could not erase credentials: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/cli/config/credentials"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/homedir"
|
||||
"github.com/pkg/errors"
|
||||
|
@ -38,15 +40,6 @@ func SetDir(dir string) {
|
|||
configDir = dir
|
||||
}
|
||||
|
||||
// NewConfigFile initializes an empty configuration file for the given filename 'fn'
|
||||
func NewConfigFile(fn string) *configfile.ConfigFile {
|
||||
return &configfile.ConfigFile{
|
||||
AuthConfigs: make(map[string]types.AuthConfig),
|
||||
HTTPHeaders: make(map[string]string),
|
||||
Filename: fn,
|
||||
}
|
||||
}
|
||||
|
||||
// LegacyLoadFromReader is a convenience function that creates a ConfigFile object from
|
||||
// a non-nested reader
|
||||
func LegacyLoadFromReader(configData io.Reader) (*configfile.ConfigFile, error) {
|
||||
|
@ -75,46 +68,53 @@ func Load(configDir string) (*configfile.ConfigFile, error) {
|
|||
configDir = Dir()
|
||||
}
|
||||
|
||||
configFile := configfile.ConfigFile{
|
||||
AuthConfigs: make(map[string]types.AuthConfig),
|
||||
Filename: filepath.Join(configDir, ConfigFileName),
|
||||
}
|
||||
filename := filepath.Join(configDir, ConfigFileName)
|
||||
configFile := configfile.New(filename)
|
||||
|
||||
// Try happy path first - latest config file
|
||||
if _, err := os.Stat(configFile.Filename); err == nil {
|
||||
file, err := os.Open(configFile.Filename)
|
||||
if _, err := os.Stat(filename); err == nil {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return &configFile, errors.Errorf("%s - %v", configFile.Filename, err)
|
||||
return configFile, errors.Errorf("%s - %v", filename, err)
|
||||
}
|
||||
defer file.Close()
|
||||
err = configFile.LoadFromReader(file)
|
||||
if err != nil {
|
||||
err = errors.Errorf("%s - %v", configFile.Filename, err)
|
||||
err = errors.Errorf("%s - %v", filename, err)
|
||||
}
|
||||
return &configFile, err
|
||||
return configFile, err
|
||||
} else if !os.IsNotExist(err) {
|
||||
// if file is there but we can't stat it for any reason other
|
||||
// than it doesn't exist then stop
|
||||
return &configFile, errors.Errorf("%s - %v", configFile.Filename, err)
|
||||
return configFile, errors.Errorf("%s - %v", filename, err)
|
||||
}
|
||||
|
||||
// Can't find latest config file so check for the old one
|
||||
confFile := filepath.Join(homedir.Get(), oldConfigfile)
|
||||
if _, err := os.Stat(confFile); err != nil {
|
||||
return &configFile, nil //missing file is not an error
|
||||
return configFile, nil //missing file is not an error
|
||||
}
|
||||
file, err := os.Open(confFile)
|
||||
if err != nil {
|
||||
return &configFile, errors.Errorf("%s - %v", confFile, err)
|
||||
return configFile, errors.Errorf("%s - %v", confFile, err)
|
||||
}
|
||||
defer file.Close()
|
||||
err = configFile.LegacyLoadFromReader(file)
|
||||
if err != nil {
|
||||
return &configFile, errors.Errorf("%s - %v", confFile, err)
|
||||
return configFile, errors.Errorf("%s - %v", confFile, err)
|
||||
}
|
||||
return configFile, nil
|
||||
}
|
||||
|
||||
if configFile.HTTPHeaders == nil {
|
||||
configFile.HTTPHeaders = map[string]string{}
|
||||
// LoadDefaultConfigFile attempts to load the default config file and returns
|
||||
// an initialized ConfigFile struct if none is found.
|
||||
func LoadDefaultConfigFile(stderr io.Writer) *configfile.ConfigFile {
|
||||
configFile, err := Load(Dir())
|
||||
if err != nil {
|
||||
fmt.Fprintf(stderr, "WARNING: Error loading config file: %v\n", err)
|
||||
}
|
||||
return &configFile, nil
|
||||
if !configFile.ContainsAuth() {
|
||||
configFile.CredentialsStore = credentials.DetectDefaultStore(configFile.CredentialsStore)
|
||||
}
|
||||
return configFile
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -8,28 +9,34 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/cli/config/credentials"
|
||||
"github.com/docker/docker/pkg/homedir"
|
||||
"github.com/docker/docker/pkg/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEmptyConfigDir(t *testing.T) {
|
||||
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpHome)
|
||||
func setupConfigDir(t *testing.T) (string, func()) {
|
||||
tmpdir, err := ioutil.TempDir("", "config-test")
|
||||
require.NoError(t, err)
|
||||
oldDir := Dir()
|
||||
SetDir(tmpdir)
|
||||
|
||||
SetDir(tmpHome)
|
||||
return tmpdir, func() {
|
||||
SetDir(oldDir)
|
||||
os.RemoveAll(tmpdir)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyConfigDir(t *testing.T) {
|
||||
tmpHome, cleanup := setupConfigDir(t)
|
||||
defer cleanup()
|
||||
|
||||
config, err := Load("")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed loading on empty config dir: %q", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedConfigFilename := filepath.Join(tmpHome, ConfigFileName)
|
||||
if config.Filename != expectedConfigFilename {
|
||||
t.Fatalf("Expected config filename %s, got %s", expectedConfigFilename, config.Filename)
|
||||
}
|
||||
assert.Equal(t, expectedConfigFilename, config.Filename)
|
||||
|
||||
// Now save it and make sure it shows up in new form
|
||||
saveConfigAndValidateNewFormat(t, config, tmpHome)
|
||||
|
@ -37,15 +44,11 @@ func TestEmptyConfigDir(t *testing.T) {
|
|||
|
||||
func TestMissingFile(t *testing.T) {
|
||||
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmpHome)
|
||||
|
||||
config, err := Load(tmpHome)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed loading on missing file: %q", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
// Now save it and make sure it shows up in new form
|
||||
saveConfigAndValidateNewFormat(t, config, tmpHome)
|
||||
|
@ -53,17 +56,13 @@ func TestMissingFile(t *testing.T) {
|
|||
|
||||
func TestSaveFileToDirs(t *testing.T) {
|
||||
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmpHome)
|
||||
|
||||
tmpHome += "/.docker"
|
||||
|
||||
config, err := Load(tmpHome)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed loading on missing file: %q", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
// Now save it and make sure it shows up in new form
|
||||
saveConfigAndValidateNewFormat(t, config, tmpHome)
|
||||
|
@ -71,38 +70,28 @@ func TestSaveFileToDirs(t *testing.T) {
|
|||
|
||||
func TestEmptyFile(t *testing.T) {
|
||||
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmpHome)
|
||||
|
||||
fn := filepath.Join(tmpHome, ConfigFileName)
|
||||
if err := ioutil.WriteFile(fn, []byte(""), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = ioutil.WriteFile(fn, []byte(""), 0600)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = Load(tmpHome)
|
||||
if err == nil {
|
||||
t.Fatalf("Was supposed to fail")
|
||||
}
|
||||
testutil.ErrorContains(t, err, "EOF")
|
||||
}
|
||||
|
||||
func TestEmptyJSON(t *testing.T) {
|
||||
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmpHome)
|
||||
|
||||
fn := filepath.Join(tmpHome, ConfigFileName)
|
||||
if err := ioutil.WriteFile(fn, []byte("{}"), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = ioutil.WriteFile(fn, []byte("{}"), 0600)
|
||||
require.NoError(t, err)
|
||||
|
||||
config, err := Load(tmpHome)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed loading on empty json file: %q", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
// Now save it and make sure it shows up in new form
|
||||
saveConfigAndValidateNewFormat(t, config, tmpHome)
|
||||
|
@ -118,9 +107,7 @@ email`: "Invalid auth configuration file",
|
|||
}
|
||||
|
||||
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmpHome)
|
||||
|
||||
homeKey := homedir.Key()
|
||||
|
@ -131,27 +118,17 @@ email`: "Invalid auth configuration file",
|
|||
|
||||
for content, expectedError := range invalids {
|
||||
fn := filepath.Join(tmpHome, oldConfigfile)
|
||||
if err := ioutil.WriteFile(fn, []byte(content), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
config, err := Load(tmpHome)
|
||||
// Use Contains instead of == since the file name will change each time
|
||||
if err == nil || !strings.Contains(err.Error(), expectedError) {
|
||||
t.Fatalf("Should have failed\nConfig: %v\nGot: %v\nExpected: %v", config, err, expectedError)
|
||||
}
|
||||
err := ioutil.WriteFile(fn, []byte(content), 0600)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = Load(tmpHome)
|
||||
testutil.ErrorContains(t, err, expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOldValidAuth(t *testing.T) {
|
||||
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmpHome)
|
||||
|
||||
homeKey := homedir.Key()
|
||||
|
@ -163,14 +140,11 @@ func TestOldValidAuth(t *testing.T) {
|
|||
fn := filepath.Join(tmpHome, oldConfigfile)
|
||||
js := `username = am9lam9lOmhlbGxv
|
||||
email = user@example.com`
|
||||
if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = ioutil.WriteFile(fn, []byte(js), 0600)
|
||||
require.NoError(t, err)
|
||||
|
||||
config, err := Load(tmpHome)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
// defaultIndexserver is https://index.docker.io/v1/
|
||||
ac := config.AuthConfigs["https://index.docker.io/v1/"]
|
||||
|
@ -189,16 +163,12 @@ func TestOldValidAuth(t *testing.T) {
|
|||
}
|
||||
}`
|
||||
|
||||
if configStr != expConfStr {
|
||||
t.Fatalf("Should have save in new form: \n%s\n not \n%s", configStr, expConfStr)
|
||||
}
|
||||
assert.Equal(t, expConfStr, configStr)
|
||||
}
|
||||
|
||||
func TestOldJSONInvalid(t *testing.T) {
|
||||
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmpHome)
|
||||
|
||||
homeKey := homedir.Key()
|
||||
|
@ -222,9 +192,7 @@ func TestOldJSONInvalid(t *testing.T) {
|
|||
|
||||
func TestOldJSON(t *testing.T) {
|
||||
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmpHome)
|
||||
|
||||
homeKey := homedir.Key()
|
||||
|
@ -240,9 +208,7 @@ func TestOldJSON(t *testing.T) {
|
|||
}
|
||||
|
||||
config, err := Load(tmpHome)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed loading on empty json file: %q", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
ac := config.AuthConfigs["https://index.docker.io/v1/"]
|
||||
if ac.Username != "joejoe" || ac.Password != "hello" {
|
||||
|
@ -268,9 +234,7 @@ func TestOldJSON(t *testing.T) {
|
|||
|
||||
func TestNewJSON(t *testing.T) {
|
||||
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmpHome)
|
||||
|
||||
fn := filepath.Join(tmpHome, ConfigFileName)
|
||||
|
@ -280,9 +244,7 @@ func TestNewJSON(t *testing.T) {
|
|||
}
|
||||
|
||||
config, err := Load(tmpHome)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed loading on empty json file: %q", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
ac := config.AuthConfigs["https://index.docker.io/v1/"]
|
||||
if ac.Username != "joejoe" || ac.Password != "hello" {
|
||||
|
@ -307,9 +269,7 @@ func TestNewJSON(t *testing.T) {
|
|||
|
||||
func TestNewJSONNoEmail(t *testing.T) {
|
||||
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmpHome)
|
||||
|
||||
fn := filepath.Join(tmpHome, ConfigFileName)
|
||||
|
@ -319,9 +279,7 @@ func TestNewJSONNoEmail(t *testing.T) {
|
|||
}
|
||||
|
||||
config, err := Load(tmpHome)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed loading on empty json file: %q", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
ac := config.AuthConfigs["https://index.docker.io/v1/"]
|
||||
if ac.Username != "joejoe" || ac.Password != "hello" {
|
||||
|
@ -346,9 +304,7 @@ func TestNewJSONNoEmail(t *testing.T) {
|
|||
|
||||
func TestJSONWithPsFormat(t *testing.T) {
|
||||
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmpHome)
|
||||
|
||||
fn := filepath.Join(tmpHome, ConfigFileName)
|
||||
|
@ -361,9 +317,7 @@ func TestJSONWithPsFormat(t *testing.T) {
|
|||
}
|
||||
|
||||
config, err := Load(tmpHome)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed loading on empty json file: %q", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
if config.PsFormat != `table {{.ID}}\t{{.Label "com.docker.label.cpu"}}` {
|
||||
t.Fatalf("Unknown ps format: %s\n", config.PsFormat)
|
||||
|
@ -379,9 +333,7 @@ func TestJSONWithPsFormat(t *testing.T) {
|
|||
|
||||
func TestJSONWithCredentialStore(t *testing.T) {
|
||||
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmpHome)
|
||||
|
||||
fn := filepath.Join(tmpHome, ConfigFileName)
|
||||
|
@ -394,9 +346,7 @@ func TestJSONWithCredentialStore(t *testing.T) {
|
|||
}
|
||||
|
||||
config, err := Load(tmpHome)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed loading on empty json file: %q", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
if config.CredentialsStore != "crazy-secure-storage" {
|
||||
t.Fatalf("Unknown credential store: %s\n", config.CredentialsStore)
|
||||
|
@ -412,9 +362,7 @@ func TestJSONWithCredentialStore(t *testing.T) {
|
|||
|
||||
func TestJSONWithCredentialHelpers(t *testing.T) {
|
||||
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmpHome)
|
||||
|
||||
fn := filepath.Join(tmpHome, ConfigFileName)
|
||||
|
@ -427,9 +375,7 @@ func TestJSONWithCredentialHelpers(t *testing.T) {
|
|||
}
|
||||
|
||||
config, err := Load(tmpHome)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed loading on empty json file: %q", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
if config.CredentialHelpers == nil {
|
||||
t.Fatal("config.CredentialHelpers was nil")
|
||||
|
@ -450,26 +396,18 @@ func TestJSONWithCredentialHelpers(t *testing.T) {
|
|||
}
|
||||
|
||||
// Save it and make sure it shows up in new form
|
||||
func saveConfigAndValidateNewFormat(t *testing.T, config *configfile.ConfigFile, homeFolder string) string {
|
||||
if err := config.Save(); err != nil {
|
||||
t.Fatalf("Failed to save: %q", err)
|
||||
}
|
||||
func saveConfigAndValidateNewFormat(t *testing.T, config *configfile.ConfigFile, configDir string) string {
|
||||
require.NoError(t, config.Save())
|
||||
|
||||
buf, err := ioutil.ReadFile(filepath.Join(homeFolder, ConfigFileName))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(buf), `"auths":`) {
|
||||
t.Fatalf("Should have save in new form: %s", string(buf))
|
||||
}
|
||||
buf, err := ioutil.ReadFile(filepath.Join(configDir, ConfigFileName))
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(buf), `"auths":`)
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
func TestConfigDir(t *testing.T) {
|
||||
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmpHome)
|
||||
|
||||
if Dir() == tmpHome {
|
||||
|
@ -484,22 +422,11 @@ func TestConfigDir(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestConfigFile(t *testing.T) {
|
||||
configFilename := "configFilename"
|
||||
configFile := NewConfigFile(configFilename)
|
||||
|
||||
if configFile.Filename != configFilename {
|
||||
t.Fatalf("Expected %s, got %s", configFilename, configFile.Filename)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONReaderNoFile(t *testing.T) {
|
||||
js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } } }`
|
||||
|
||||
config, err := LoadFromReader(strings.NewReader(js))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed loading on empty json file: %q", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
ac := config.AuthConfigs["https://index.docker.io/v1/"]
|
||||
if ac.Username != "joejoe" || ac.Password != "hello" {
|
||||
|
@ -512,9 +439,7 @@ func TestOldJSONReaderNoFile(t *testing.T) {
|
|||
js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
|
||||
|
||||
config, err := LegacyLoadFromReader(strings.NewReader(js))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed loading on empty json file: %q", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
ac := config.AuthConfigs["https://index.docker.io/v1/"]
|
||||
if ac.Username != "joejoe" || ac.Password != "hello" {
|
||||
|
@ -528,9 +453,7 @@ func TestJSONWithPsFormatNoFile(t *testing.T) {
|
|||
"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
|
||||
}`
|
||||
config, err := LoadFromReader(strings.NewReader(js))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed loading on empty json file: %q", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
if config.PsFormat != `table {{.ID}}\t{{.Label "com.docker.label.cpu"}}` {
|
||||
t.Fatalf("Unknown ps format: %s\n", config.PsFormat)
|
||||
|
@ -546,28 +469,19 @@ func TestJSONSaveWithNoFile(t *testing.T) {
|
|||
config, err := LoadFromReader(strings.NewReader(js))
|
||||
require.NoError(t, err)
|
||||
err = config.Save()
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error. File should not have been able to save with no file name.")
|
||||
}
|
||||
testutil.ErrorContains(t, err, "with empty filename")
|
||||
|
||||
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create a temp dir: %q", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmpHome)
|
||||
|
||||
fn := filepath.Join(tmpHome, ConfigFileName)
|
||||
f, _ := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
defer f.Close()
|
||||
|
||||
err = config.SaveToWriter(f)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed saving to file: %q", err)
|
||||
}
|
||||
require.NoError(t, config.SaveToWriter(f))
|
||||
buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
expConfStr := `{
|
||||
"auths": {
|
||||
"https://index.docker.io/v1/": {
|
||||
|
@ -582,32 +496,23 @@ func TestJSONSaveWithNoFile(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestLegacyJSONSaveWithNoFile(t *testing.T) {
|
||||
|
||||
js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
|
||||
config, err := LegacyLoadFromReader(strings.NewReader(js))
|
||||
require.NoError(t, err)
|
||||
err = config.Save()
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error. File should not have been able to save with no file name.")
|
||||
}
|
||||
testutil.ErrorContains(t, err, "with empty filename")
|
||||
|
||||
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create a temp dir: %q", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmpHome)
|
||||
|
||||
fn := filepath.Join(tmpHome, ConfigFileName)
|
||||
f, _ := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
defer f.Close()
|
||||
|
||||
if err = config.SaveToWriter(f); err != nil {
|
||||
t.Fatalf("Failed saving to file: %q", err)
|
||||
}
|
||||
require.NoError(t, config.SaveToWriter(f))
|
||||
buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
expConfStr := `{
|
||||
"auths": {
|
||||
|
@ -622,3 +527,22 @@ func TestLegacyJSONSaveWithNoFile(t *testing.T) {
|
|||
t.Fatalf("Should have save in new form: \n%s\n not \n%s", string(buf), expConfStr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadDefaultConfigFile(t *testing.T) {
|
||||
dir, cleanup := setupConfigDir(t)
|
||||
defer cleanup()
|
||||
buffer := new(bytes.Buffer)
|
||||
|
||||
filename := filepath.Join(dir, ConfigFileName)
|
||||
content := []byte(`{"PsFormat": "format"}`)
|
||||
err := ioutil.WriteFile(filename, content, 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
configFile := LoadDefaultConfigFile(buffer)
|
||||
credStore := credentials.DetectDefaultStore("")
|
||||
expected := configfile.New(filename)
|
||||
expected.CredentialsStore = credStore
|
||||
expected.PsFormat = "format"
|
||||
|
||||
assert.Equal(t, expected, configFile)
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/config/credentials"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/pkg/errors"
|
||||
|
@ -53,6 +54,15 @@ type ProxyConfig struct {
|
|||
FTPProxy string `json:"ftpProxy,omitempty"`
|
||||
}
|
||||
|
||||
// New initializes an empty configuration file for the given filename 'fn'
|
||||
func New(fn string) *ConfigFile {
|
||||
return &ConfigFile{
|
||||
AuthConfigs: make(map[string]types.AuthConfig),
|
||||
HTTPHeaders: make(map[string]string),
|
||||
Filename: fn,
|
||||
}
|
||||
}
|
||||
|
||||
// LegacyLoadFromReader reads the non-nested configuration data given and sets up the
|
||||
// auth config information with given directory and populates the receiver object
|
||||
func (configFile *ConfigFile) LegacyLoadFromReader(configData io.Reader) error {
|
||||
|
@ -118,6 +128,11 @@ func (configFile *ConfigFile) ContainsAuth() bool {
|
|||
len(configFile.AuthConfigs) > 0
|
||||
}
|
||||
|
||||
// GetAuthConfigs returns the mapping of repo to auth configuration
|
||||
func (configFile *ConfigFile) GetAuthConfigs() map[string]types.AuthConfig {
|
||||
return configFile.AuthConfigs
|
||||
}
|
||||
|
||||
// SaveToWriter encodes and writes out all the authorization information to
|
||||
// the given writer
|
||||
func (configFile *ConfigFile) SaveToWriter(writer io.Writer) error {
|
||||
|
@ -231,3 +246,56 @@ func decodeAuth(authStr string) (string, string, error) {
|
|||
password := strings.Trim(arr[1], "\x00")
|
||||
return arr[0], password, nil
|
||||
}
|
||||
|
||||
// GetCredentialsStore returns a new credentials store from the settings in the
|
||||
// configuration file
|
||||
func (configFile *ConfigFile) GetCredentialsStore(serverAddress string) credentials.Store {
|
||||
if helper := getConfiguredCredentialStore(configFile, serverAddress); helper != "" {
|
||||
return credentials.NewNativeStore(configFile, helper)
|
||||
}
|
||||
return credentials.NewFileStore(configFile)
|
||||
}
|
||||
|
||||
// GetAuthConfig for a repository from the credential store
|
||||
func (configFile *ConfigFile) GetAuthConfig(serverAddress string) (types.AuthConfig, error) {
|
||||
return configFile.GetCredentialsStore(serverAddress).Get(serverAddress)
|
||||
}
|
||||
|
||||
// getConfiguredCredentialStore returns the credential helper configured for the
|
||||
// given registry, the default credsStore, or the empty string if neither are
|
||||
// configured.
|
||||
func getConfiguredCredentialStore(c *ConfigFile, serverAddress string) string {
|
||||
if c.CredentialHelpers != nil && serverAddress != "" {
|
||||
if helper, exists := c.CredentialHelpers[serverAddress]; exists {
|
||||
return helper
|
||||
}
|
||||
}
|
||||
return c.CredentialsStore
|
||||
}
|
||||
|
||||
// GetAllCredentials returns all of the credentials stored in all of the
|
||||
// configured credential stores.
|
||||
func (configFile *ConfigFile) GetAllCredentials() (map[string]types.AuthConfig, error) {
|
||||
auths := make(map[string]types.AuthConfig)
|
||||
addAll := func(from map[string]types.AuthConfig) {
|
||||
for reg, ac := range from {
|
||||
auths[reg] = ac
|
||||
}
|
||||
}
|
||||
|
||||
for registry := range configFile.CredentialHelpers {
|
||||
helper := configFile.GetCredentialsStore(registry)
|
||||
newAuths, err := helper.GetAll()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addAll(newAuths)
|
||||
}
|
||||
defaultStore := configFile.GetCredentialsStore("")
|
||||
newAuths, err := defaultStore.GetAll()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addAll(newAuths)
|
||||
return auths, nil
|
||||
}
|
||||
|
|
|
@ -6,26 +6,18 @@ import (
|
|||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEncodeAuth(t *testing.T) {
|
||||
newAuthConfig := &types.AuthConfig{Username: "ken", Password: "test"}
|
||||
authStr := encodeAuth(newAuthConfig)
|
||||
decAuthConfig := &types.AuthConfig{}
|
||||
|
||||
expected := &types.AuthConfig{}
|
||||
var err error
|
||||
decAuthConfig.Username, decAuthConfig.Password, err = decodeAuth(authStr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if newAuthConfig.Username != decAuthConfig.Username {
|
||||
t.Fatal("Encode Username doesn't match decoded Username")
|
||||
}
|
||||
if newAuthConfig.Password != decAuthConfig.Password {
|
||||
t.Fatal("Encode Password doesn't match decoded Password")
|
||||
}
|
||||
if authStr != "a2VuOnRlc3Q=" {
|
||||
t.Fatal("AuthString encoding isn't correct.")
|
||||
}
|
||||
expected.Username, expected.Password, err = decodeAuth(authStr)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expected, newAuthConfig)
|
||||
}
|
||||
|
||||
func TestProxyConfig(t *testing.T) {
|
||||
|
@ -142,3 +134,26 @@ func TestProxyConfigPerHost(t *testing.T) {
|
|||
}
|
||||
assert.Equal(t, expected, proxyConfig)
|
||||
}
|
||||
|
||||
func TestConfigFile(t *testing.T) {
|
||||
configFilename := "configFilename"
|
||||
configFile := New(configFilename)
|
||||
|
||||
assert.Equal(t, configFilename, configFile.Filename)
|
||||
}
|
||||
|
||||
func TestGetAllCredentials(t *testing.T) {
|
||||
configFile := New("filename")
|
||||
exampleAuth := types.AuthConfig{
|
||||
Username: "user",
|
||||
Password: "pass",
|
||||
}
|
||||
configFile.AuthConfigs["example.com/foo"] = exampleAuth
|
||||
|
||||
authConfigs, err := configFile.GetAllCredentials()
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := make(map[string]types.AuthConfig)
|
||||
expected["example.com/foo"] = exampleAuth
|
||||
assert.Equal(t, expected, authConfigs)
|
||||
}
|
||||
|
|
|
@ -2,21 +2,18 @@ package credentials
|
|||
|
||||
import (
|
||||
"os/exec"
|
||||
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
)
|
||||
|
||||
// DetectDefaultStore sets the default credentials store
|
||||
// if the host includes the default store helper program.
|
||||
func DetectDefaultStore(c *configfile.ConfigFile) {
|
||||
if c.CredentialsStore != "" {
|
||||
// user defined
|
||||
return
|
||||
// DetectDefaultStore return the default credentials store for the platform if
|
||||
// the store executable is available.
|
||||
func DetectDefaultStore(store string) string {
|
||||
// user defined or no default for platform
|
||||
if store != "" || defaultCredentialsStore == "" {
|
||||
return store
|
||||
}
|
||||
|
||||
if defaultCredentialsStore != "" {
|
||||
if _, err := exec.LookPath(remoteCredentialsPrefix + defaultCredentialsStore); err == nil {
|
||||
c.CredentialsStore = defaultCredentialsStore
|
||||
}
|
||||
return defaultCredentialsStore
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
|
|
@ -1,37 +1,39 @@
|
|||
package credentials
|
||||
|
||||
import (
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/registry"
|
||||
)
|
||||
|
||||
type store interface {
|
||||
Save() error
|
||||
GetAuthConfigs() map[string]types.AuthConfig
|
||||
}
|
||||
|
||||
// fileStore implements a credentials store using
|
||||
// the docker configuration file to keep the credentials in plain text.
|
||||
type fileStore struct {
|
||||
file *configfile.ConfigFile
|
||||
file store
|
||||
}
|
||||
|
||||
// NewFileStore creates a new file credentials store.
|
||||
func NewFileStore(file *configfile.ConfigFile) Store {
|
||||
return &fileStore{
|
||||
file: file,
|
||||
}
|
||||
func NewFileStore(file store) Store {
|
||||
return &fileStore{file: file}
|
||||
}
|
||||
|
||||
// Erase removes the given credentials from the file store.
|
||||
func (c *fileStore) Erase(serverAddress string) error {
|
||||
delete(c.file.AuthConfigs, serverAddress)
|
||||
delete(c.file.GetAuthConfigs(), serverAddress)
|
||||
return c.file.Save()
|
||||
}
|
||||
|
||||
// Get retrieves credentials for a specific server from the file store.
|
||||
func (c *fileStore) Get(serverAddress string) (types.AuthConfig, error) {
|
||||
authConfig, ok := c.file.AuthConfigs[serverAddress]
|
||||
authConfig, ok := c.file.GetAuthConfigs()[serverAddress]
|
||||
if !ok {
|
||||
// Maybe they have a legacy config file, we will iterate the keys converting
|
||||
// them to the new format and testing
|
||||
for r, ac := range c.file.AuthConfigs {
|
||||
for r, ac := range c.file.GetAuthConfigs() {
|
||||
if serverAddress == registry.ConvertToHostname(r) {
|
||||
return ac, nil
|
||||
}
|
||||
|
@ -43,11 +45,11 @@ func (c *fileStore) Get(serverAddress string) (types.AuthConfig, error) {
|
|||
}
|
||||
|
||||
func (c *fileStore) GetAll() (map[string]types.AuthConfig, error) {
|
||||
return c.file.AuthConfigs, nil
|
||||
return c.file.GetAuthConfigs(), nil
|
||||
}
|
||||
|
||||
// Store saves the given credentials in the file store.
|
||||
func (c *fileStore) Store(authConfig types.AuthConfig) error {
|
||||
c.file.AuthConfigs[authConfig.ServerAddress] = authConfig
|
||||
c.file.GetAuthConfigs()[authConfig.ServerAddress] = authConfig
|
||||
return c.file.Save()
|
||||
}
|
||||
|
|
|
@ -1,56 +1,49 @@
|
|||
package credentials
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
cliconfig "github.com/docker/cli/cli/config"
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func newConfigFile(auths map[string]types.AuthConfig) *configfile.ConfigFile {
|
||||
tmp, _ := ioutil.TempFile("", "docker-test")
|
||||
name := tmp.Name()
|
||||
tmp.Close()
|
||||
type fakeStore struct {
|
||||
configs map[string]types.AuthConfig
|
||||
}
|
||||
|
||||
c := cliconfig.NewConfigFile(name)
|
||||
c.AuthConfigs = auths
|
||||
return c
|
||||
func (f *fakeStore) Save() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeStore) GetAuthConfigs() map[string]types.AuthConfig {
|
||||
return f.configs
|
||||
}
|
||||
|
||||
func newStore(auths map[string]types.AuthConfig) store {
|
||||
return &fakeStore{configs: auths}
|
||||
}
|
||||
|
||||
func TestFileStoreAddCredentials(t *testing.T) {
|
||||
f := newConfigFile(make(map[string]types.AuthConfig))
|
||||
f := newStore(make(map[string]types.AuthConfig))
|
||||
|
||||
s := NewFileStore(f)
|
||||
err := s.Store(types.AuthConfig{
|
||||
auth := types.AuthConfig{
|
||||
Auth: "super_secret_token",
|
||||
Email: "foo@example.com",
|
||||
ServerAddress: "https://example.com",
|
||||
})
|
||||
}
|
||||
err := s.Store(auth)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, f.GetAuthConfigs(), 1)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(f.AuthConfigs) != 1 {
|
||||
t.Fatalf("expected 1 auth config, got %d", len(f.AuthConfigs))
|
||||
}
|
||||
|
||||
a, ok := f.AuthConfigs["https://example.com"]
|
||||
if !ok {
|
||||
t.Fatalf("expected auth for https://example.com, got %v", f.AuthConfigs)
|
||||
}
|
||||
if a.Auth != "super_secret_token" {
|
||||
t.Fatalf("expected auth `super_secret_token`, got %s", a.Auth)
|
||||
}
|
||||
if a.Email != "foo@example.com" {
|
||||
t.Fatalf("expected email `foo@example.com`, got %s", a.Email)
|
||||
}
|
||||
actual, ok := f.GetAuthConfigs()["https://example.com"]
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, auth, actual)
|
||||
}
|
||||
|
||||
func TestFileStoreGet(t *testing.T) {
|
||||
f := newConfigFile(map[string]types.AuthConfig{
|
||||
f := newStore(map[string]types.AuthConfig{
|
||||
"https://example.com": {
|
||||
Auth: "super_secret_token",
|
||||
Email: "foo@example.com",
|
||||
|
@ -74,7 +67,7 @@ func TestFileStoreGet(t *testing.T) {
|
|||
func TestFileStoreGetAll(t *testing.T) {
|
||||
s1 := "https://example.com"
|
||||
s2 := "https://example2.com"
|
||||
f := newConfigFile(map[string]types.AuthConfig{
|
||||
f := newStore(map[string]types.AuthConfig{
|
||||
s1: {
|
||||
Auth: "super_secret_token",
|
||||
Email: "foo@example.com",
|
||||
|
@ -110,7 +103,7 @@ func TestFileStoreGetAll(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFileStoreErase(t *testing.T) {
|
||||
f := newConfigFile(map[string]types.AuthConfig{
|
||||
f := newStore(map[string]types.AuthConfig{
|
||||
"https://example.com": {
|
||||
Auth: "super_secret_token",
|
||||
Email: "foo@example.com",
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package credentials
|
||||
|
||||
import (
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/docker-credential-helpers/client"
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
"github.com/docker/docker/api/types"
|
||||
|
@ -22,7 +21,7 @@ type nativeStore struct {
|
|||
|
||||
// NewNativeStore creates a new native store that
|
||||
// uses a remote helper program to manage credentials.
|
||||
func NewNativeStore(file *configfile.ConfigFile, helperSuffix string) Store {
|
||||
func NewNativeStore(file store, helperSuffix string) Store {
|
||||
name := remoteCredentialsPrefix + helperSuffix
|
||||
return &nativeStore{
|
||||
programFunc: client.NewShellProgramFunc(name),
|
||||
|
|
|
@ -11,7 +11,10 @@ import (
|
|||
"github.com/docker/docker-credential-helpers/client"
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/testutil"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -90,53 +93,32 @@ func mockCommandFn(args ...string) client.Program {
|
|||
}
|
||||
|
||||
func TestNativeStoreAddCredentials(t *testing.T) {
|
||||
f := newConfigFile(make(map[string]types.AuthConfig))
|
||||
f.CredentialsStore = "mock"
|
||||
|
||||
f := newStore(make(map[string]types.AuthConfig))
|
||||
s := &nativeStore{
|
||||
programFunc: mockCommandFn,
|
||||
fileStore: NewFileStore(f),
|
||||
}
|
||||
err := s.Store(types.AuthConfig{
|
||||
auth := types.AuthConfig{
|
||||
Username: "foo",
|
||||
Password: "bar",
|
||||
Email: "foo@example.com",
|
||||
ServerAddress: validServerAddress,
|
||||
})
|
||||
}
|
||||
err := s.Store(auth)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, f.GetAuthConfigs(), 1)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(f.AuthConfigs) != 1 {
|
||||
t.Fatalf("expected 1 auth config, got %d", len(f.AuthConfigs))
|
||||
}
|
||||
|
||||
a, ok := f.AuthConfigs[validServerAddress]
|
||||
if !ok {
|
||||
t.Fatalf("expected auth for %s, got %v", validServerAddress, f.AuthConfigs)
|
||||
}
|
||||
if a.Auth != "" {
|
||||
t.Fatalf("expected auth to be empty, got %s", a.Auth)
|
||||
}
|
||||
if a.Username != "" {
|
||||
t.Fatalf("expected username to be empty, got %s", a.Username)
|
||||
}
|
||||
if a.Password != "" {
|
||||
t.Fatalf("expected password to be empty, got %s", a.Password)
|
||||
}
|
||||
if a.IdentityToken != "" {
|
||||
t.Fatalf("expected identity token to be empty, got %s", a.IdentityToken)
|
||||
}
|
||||
if a.Email != "foo@example.com" {
|
||||
t.Fatalf("expected email `foo@example.com`, got %s", a.Email)
|
||||
actual, ok := f.GetAuthConfigs()[validServerAddress]
|
||||
assert.True(t, ok)
|
||||
expected := types.AuthConfig{
|
||||
Email: auth.Email,
|
||||
ServerAddress: auth.ServerAddress,
|
||||
}
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestNativeStoreAddInvalidCredentials(t *testing.T) {
|
||||
f := newConfigFile(make(map[string]types.AuthConfig))
|
||||
f.CredentialsStore = "mock"
|
||||
|
||||
f := newStore(make(map[string]types.AuthConfig))
|
||||
s := &nativeStore{
|
||||
programFunc: mockCommandFn,
|
||||
fileStore: NewFileStore(f),
|
||||
|
@ -147,102 +129,66 @@ func TestNativeStoreAddInvalidCredentials(t *testing.T) {
|
|||
Email: "foo@example.com",
|
||||
ServerAddress: invalidServerAddress,
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "program failed") {
|
||||
t.Fatalf("expected `program failed`, got %v", err)
|
||||
}
|
||||
|
||||
if len(f.AuthConfigs) != 0 {
|
||||
t.Fatalf("expected 0 auth config, got %d", len(f.AuthConfigs))
|
||||
}
|
||||
testutil.ErrorContains(t, err, "program failed")
|
||||
assert.Len(t, f.GetAuthConfigs(), 0)
|
||||
}
|
||||
|
||||
func TestNativeStoreGet(t *testing.T) {
|
||||
f := newConfigFile(map[string]types.AuthConfig{
|
||||
f := newStore(map[string]types.AuthConfig{
|
||||
validServerAddress: {
|
||||
Email: "foo@example.com",
|
||||
},
|
||||
})
|
||||
f.CredentialsStore = "mock"
|
||||
|
||||
s := &nativeStore{
|
||||
programFunc: mockCommandFn,
|
||||
fileStore: NewFileStore(f),
|
||||
}
|
||||
a, err := s.Get(validServerAddress)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual, err := s.Get(validServerAddress)
|
||||
require.NoError(t, err)
|
||||
|
||||
if a.Username != "foo" {
|
||||
t.Fatalf("expected username `foo`, got %s", a.Username)
|
||||
}
|
||||
if a.Password != "bar" {
|
||||
t.Fatalf("expected password `bar`, got %s", a.Password)
|
||||
}
|
||||
if a.IdentityToken != "" {
|
||||
t.Fatalf("expected identity token to be empty, got %s", a.IdentityToken)
|
||||
}
|
||||
if a.Email != "foo@example.com" {
|
||||
t.Fatalf("expected email `foo@example.com`, got %s", a.Email)
|
||||
expected := types.AuthConfig{
|
||||
Username: "foo",
|
||||
Password: "bar",
|
||||
Email: "foo@example.com",
|
||||
}
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestNativeStoreGetIdentityToken(t *testing.T) {
|
||||
f := newConfigFile(map[string]types.AuthConfig{
|
||||
f := newStore(map[string]types.AuthConfig{
|
||||
validServerAddress2: {
|
||||
Email: "foo@example2.com",
|
||||
},
|
||||
})
|
||||
f.CredentialsStore = "mock"
|
||||
|
||||
s := &nativeStore{
|
||||
programFunc: mockCommandFn,
|
||||
fileStore: NewFileStore(f),
|
||||
}
|
||||
a, err := s.Get(validServerAddress2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual, err := s.Get(validServerAddress2)
|
||||
require.NoError(t, err)
|
||||
|
||||
if a.Username != "" {
|
||||
t.Fatalf("expected username to be empty, got %s", a.Username)
|
||||
}
|
||||
if a.Password != "" {
|
||||
t.Fatalf("expected password to be empty, got %s", a.Password)
|
||||
}
|
||||
if a.IdentityToken != "abcd1234" {
|
||||
t.Fatalf("expected identity token `abcd1234`, got %s", a.IdentityToken)
|
||||
}
|
||||
if a.Email != "foo@example2.com" {
|
||||
t.Fatalf("expected email `foo@example2.com`, got %s", a.Email)
|
||||
expected := types.AuthConfig{
|
||||
IdentityToken: "abcd1234",
|
||||
Email: "foo@example2.com",
|
||||
}
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestNativeStoreGetAll(t *testing.T) {
|
||||
f := newConfigFile(map[string]types.AuthConfig{
|
||||
f := newStore(map[string]types.AuthConfig{
|
||||
validServerAddress: {
|
||||
Email: "foo@example.com",
|
||||
},
|
||||
})
|
||||
f.CredentialsStore = "mock"
|
||||
|
||||
s := &nativeStore{
|
||||
programFunc: mockCommandFn,
|
||||
fileStore: NewFileStore(f),
|
||||
}
|
||||
as, err := s.GetAll()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(as) != 2 {
|
||||
t.Fatalf("wanted 2, got %d", len(as))
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, as, 2)
|
||||
|
||||
if as[validServerAddress].Username != "foo" {
|
||||
t.Fatalf("expected username `foo` for %s, got %s", validServerAddress, as[validServerAddress].Username)
|
||||
|
@ -271,86 +217,62 @@ func TestNativeStoreGetAll(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNativeStoreGetMissingCredentials(t *testing.T) {
|
||||
f := newConfigFile(map[string]types.AuthConfig{
|
||||
f := newStore(map[string]types.AuthConfig{
|
||||
validServerAddress: {
|
||||
Email: "foo@example.com",
|
||||
},
|
||||
})
|
||||
f.CredentialsStore = "mock"
|
||||
|
||||
s := &nativeStore{
|
||||
programFunc: mockCommandFn,
|
||||
fileStore: NewFileStore(f),
|
||||
}
|
||||
_, err := s.Get(missingCredsAddress)
|
||||
if err != nil {
|
||||
// missing credentials do not produce an error
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestNativeStoreGetInvalidAddress(t *testing.T) {
|
||||
f := newConfigFile(map[string]types.AuthConfig{
|
||||
f := newStore(map[string]types.AuthConfig{
|
||||
validServerAddress: {
|
||||
Email: "foo@example.com",
|
||||
},
|
||||
})
|
||||
f.CredentialsStore = "mock"
|
||||
|
||||
s := &nativeStore{
|
||||
programFunc: mockCommandFn,
|
||||
fileStore: NewFileStore(f),
|
||||
}
|
||||
_, err := s.Get(invalidServerAddress)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "program failed") {
|
||||
t.Fatalf("expected `program failed`, got %v", err)
|
||||
}
|
||||
testutil.ErrorContains(t, err, "program failed")
|
||||
}
|
||||
|
||||
func TestNativeStoreErase(t *testing.T) {
|
||||
f := newConfigFile(map[string]types.AuthConfig{
|
||||
f := newStore(map[string]types.AuthConfig{
|
||||
validServerAddress: {
|
||||
Email: "foo@example.com",
|
||||
},
|
||||
})
|
||||
f.CredentialsStore = "mock"
|
||||
|
||||
s := &nativeStore{
|
||||
programFunc: mockCommandFn,
|
||||
fileStore: NewFileStore(f),
|
||||
}
|
||||
err := s.Erase(validServerAddress)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(f.AuthConfigs) != 0 {
|
||||
t.Fatalf("expected 0 auth configs, got %d", len(f.AuthConfigs))
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, f.GetAuthConfigs(), 0)
|
||||
}
|
||||
|
||||
func TestNativeStoreEraseInvalidAddress(t *testing.T) {
|
||||
f := newConfigFile(map[string]types.AuthConfig{
|
||||
f := newStore(map[string]types.AuthConfig{
|
||||
validServerAddress: {
|
||||
Email: "foo@example.com",
|
||||
},
|
||||
})
|
||||
f.CredentialsStore = "mock"
|
||||
|
||||
s := &nativeStore{
|
||||
programFunc: mockCommandFn,
|
||||
fileStore: NewFileStore(f),
|
||||
}
|
||||
err := s.Erase(invalidServerAddress)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "program failed") {
|
||||
t.Fatalf("expected `program failed`, got %v", err)
|
||||
}
|
||||
testutil.ErrorContains(t, err, "program failed")
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/cli/config/credentials"
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
|
@ -19,7 +18,6 @@ type FakeCli struct {
|
|||
out *command.OutStream
|
||||
err io.Writer
|
||||
in *command.InStream
|
||||
store credentials.Store
|
||||
}
|
||||
|
||||
// NewFakeCli returns a Cli backed by the fakeCli
|
||||
|
@ -71,11 +69,3 @@ func (c *FakeCli) In() *command.InStream {
|
|||
func (c *FakeCli) ConfigFile() *configfile.ConfigFile {
|
||||
return c.configfile
|
||||
}
|
||||
|
||||
// CredentialsStore returns the fake store the cli will use
|
||||
func (c *FakeCli) CredentialsStore(serverAddress string) credentials.Store {
|
||||
if c.store == nil {
|
||||
c.store = NewFakeStore()
|
||||
}
|
||||
return c.store
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue