Merge pull request #227 from dnephin/expose-config

expose config credentials without needing the Cli
This commit is contained in:
Vincent Demeester 2017-06-29 19:11:37 +02:00 committed by GitHub
commit 74af31be7f
19 changed files with 339 additions and 484 deletions

View File

@ -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)

View File

@ -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,

View File

@ -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)

View File

@ -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",

View File

@ -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()

View File

@ -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())

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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)
}
if configFile.HTTPHeaders == nil {
configFile.HTTPHeaders = map[string]string{}
}
return &configFile, nil
return configFile, nil
}
// 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)
}
if !configFile.ContainsAuth() {
configFile.CredentialsStore = credentials.DetectDefaultStore(configFile.CredentialsStore)
}
return configFile
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}
if _, err := exec.LookPath(remoteCredentialsPrefix + defaultCredentialsStore); err == nil {
return defaultCredentialsStore
}
return ""
}

View File

@ -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()
}

View File

@ -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",

View File

@ -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),

View File

@ -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")
}

View File

@ -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
}