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