Merge component 'cli' from git@github.com:docker/cli master
This commit is contained in:
@@ -1,4 +1,12 @@
|
||||
# if you want to ignore files created by your editor/tools,
|
||||
# please consider a global .gitignore https://help.github.com/articles/ignoring-files
|
||||
*.exe
|
||||
*.exe~
|
||||
*.orig
|
||||
.*.swp
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
.editorconfig
|
||||
/build/
|
||||
cli/winresources/rsrc_386.syso
|
||||
cli/winresources/rsrc_amd64.syso
|
||||
|
||||
@@ -232,6 +232,7 @@ Kai Qiang Wu (Kennan) <wkq5325@gmail.com>
|
||||
Kai Qiang Wu (Kennan) <wkq5325@gmail.com> <wkqwu@cn.ibm.com>
|
||||
Kamil Domański <kamil@domanski.co>
|
||||
Kamjar Gerami <kami.gerami@gmail.com>
|
||||
Kat Samperi <kat.samperi@gmail.com> <kizzie@users.noreply.github.com>
|
||||
Ken Cochrane <kencochrane@gmail.com> <KenCochrane@gmail.com>
|
||||
Ken Herner <kherner@progress.com> <chosenken@gmail.com>
|
||||
Kenfe-Mickaël Laventure <mickael.laventure@gmail.com>
|
||||
@@ -281,6 +282,7 @@ Martin Redmond <redmond.martin@gmail.com> <xgithub@redmond5.com>
|
||||
Mary Anthony <mary.anthony@docker.com> <mary@docker.com>
|
||||
Mary Anthony <mary.anthony@docker.com> <moxieandmore@gmail.com>
|
||||
Mary Anthony <mary.anthony@docker.com> moxiegirl <mary@docker.com>
|
||||
Mateusz Major <apkd@users.noreply.github.com>
|
||||
Matt Bentley <matt.bentley@docker.com> <mbentley@mbentley.net>
|
||||
Matt Schurenko <matt.schurenko@gmail.com>
|
||||
Matt Williams <mattyw@me.com>
|
||||
@@ -397,6 +399,7 @@ Thatcher Peskens <thatcher@docker.com>
|
||||
Thatcher Peskens <thatcher@docker.com> <thatcher@dotcloud.com>
|
||||
Thatcher Peskens <thatcher@docker.com> <thatcher@gmx.net>
|
||||
Thomas Gazagnaire <thomas@gazagnaire.org> <thomas@gazagnaire.com>
|
||||
Thomas Krzero <thomas.kovatchitch@gmail.com>
|
||||
Thomas Léveil <thomasleveil@gmail.com>
|
||||
Thomas Léveil <thomasleveil@gmail.com> <thomasleveil@users.noreply.github.com>
|
||||
Tibor Vass <teabee89@gmail.com> <tibor@docker.com>
|
||||
|
||||
@@ -17,6 +17,7 @@ Aidan Feldman <aidan.feldman@gmail.com>
|
||||
Aidan Hobson Sayers <aidanhs@cantab.net>
|
||||
AJ Bowen <aj@gandi.net>
|
||||
Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
|
||||
Akim Demaille <akim.demaille@docker.com>
|
||||
Alan Thompson <cloojure@gmail.com>
|
||||
Albert Callarisa <shark234@gmail.com>
|
||||
Aleksa Sarai <asarai@suse.de>
|
||||
@@ -107,6 +108,7 @@ Christophe Robin <crobin@nekoo.com>
|
||||
Christophe Vidal <kriss@krizalys.com>
|
||||
Christopher Biscardi <biscarch@sketcht.com>
|
||||
Christopher Jones <tophj@linux.vnet.ibm.com>
|
||||
Christy Perez <christy@linux.vnet.ibm.com>
|
||||
Chun Chen <ramichen@tencent.com>
|
||||
Clinton Kitson <clintonskitson@gmail.com>
|
||||
Coenraad Loubser <coenraad@wish.org.za>
|
||||
@@ -178,6 +180,7 @@ Eric-Olivier Lamey <eo@lamey.me>
|
||||
Erica Windisch <erica@windisch.us>
|
||||
Erik Hollensbe <github@hollensbe.org>
|
||||
Erik St. Martin <alakriti@gmail.com>
|
||||
Ethan Haynes <ethanhaynes@alumni.harvard.edu>
|
||||
Eugene Yakubovich <eugene.yakubovich@coreos.com>
|
||||
Evan Allrich <evan@unguku.com>
|
||||
Evan Hazlett <ejhazlett@gmail.com>
|
||||
@@ -234,6 +237,7 @@ Ilya Khlopotov <ilya.khlopotov@gmail.com>
|
||||
Ilya Sotkov <ilya@sotkov.com>
|
||||
Isabel Jimenez <contact.isabeljimenez@gmail.com>
|
||||
Ivan Grcic <igrcic@gmail.com>
|
||||
Ivan Markin <sw@nogoegst.net>
|
||||
Jacob Atzen <jacob@jacobatzen.dk>
|
||||
Jacob Tomlinson <jacob@tom.linson.uk>
|
||||
Jaivish Kothari <janonymous.codevulture@gmail.com>
|
||||
@@ -314,6 +318,7 @@ Kai Qiang Wu (Kennan) <wkq5325@gmail.com>
|
||||
Kara Alexandra <kalexandra@us.ibm.com>
|
||||
Kareem Khazem <karkhaz@karkhaz.com>
|
||||
Karthik Nayak <Karthik.188@gmail.com>
|
||||
Kat Samperi <kat.samperi@gmail.com>
|
||||
Katie McLaughlin <katie@glasnt.com>
|
||||
Ke Xu <leonhartx.k@gmail.com>
|
||||
Kei Ohmura <ohmura.kei@gmail.com>
|
||||
@@ -380,6 +385,7 @@ Mark Oates <fl0yd@me.com>
|
||||
Martin Mosegaard Amdisen <martin.amdisen@praqma.com>
|
||||
Mary Anthony <mary.anthony@docker.com>
|
||||
Mason Malone <mason.malone@gmail.com>
|
||||
Mateusz Major <apkd@users.noreply.github.com>
|
||||
Matt Gucci <matt9ucci@gmail.com>
|
||||
Matt Robenolt <matt@ydekproductions.com>
|
||||
Matthew Heon <mheon@redhat.com>
|
||||
@@ -420,6 +426,7 @@ Moorthy RS <rsmoorthy@gmail.com>
|
||||
Morgan Bauer <mbauer@us.ibm.com>
|
||||
Moysés Borges <moysesb@gmail.com>
|
||||
Mrunal Patel <mrunalp@gmail.com>
|
||||
muicoder <muicoder@gmail.com>
|
||||
Muthukumar R <muthur@gmail.com>
|
||||
Máximo Cuadros <mcuadros@gmail.com>
|
||||
Nace Oroz <orkica@gmail.com>
|
||||
@@ -520,6 +527,7 @@ Shukui Yang <yangshukui@huawei.com>
|
||||
Sian Lerk Lau <kiawin@gmail.com>
|
||||
Sidhartha Mani <sidharthamn@gmail.com>
|
||||
sidharthamani <sid@rancher.com>
|
||||
Silvin Lubecki <silvin.lubecki@docker.com>
|
||||
Simei He <hesimei@zju.edu.cn>
|
||||
Simon Ferquel <simon.ferquel@docker.com>
|
||||
Sindhu S <sindhus@live.in>
|
||||
@@ -538,6 +546,7 @@ Steve Durrheimer <s.durrheimer@gmail.com>
|
||||
Steven Burgess <steven.a.burgess@hotmail.com>
|
||||
Subhajit Ghosh <isubuz.g@gmail.com>
|
||||
Sun Jianbo <wonderflow.sun@gmail.com>
|
||||
Sungwon Han <sungwon.han@navercorp.com>
|
||||
Sven Dowideit <SvenDowideit@home.org.au>
|
||||
Sylvain Baubeau <sbaubeau@redhat.com>
|
||||
Sébastien HOUZÉ <cto@verylastroom.com>
|
||||
@@ -546,6 +555,7 @@ TAGOMORI Satoshi <tagomoris@gmail.com>
|
||||
Taylor Jones <monitorjbl@gmail.com>
|
||||
Thatcher Peskens <thatcher@docker.com>
|
||||
Thomas Gazagnaire <thomas@gazagnaire.org>
|
||||
Thomas Krzero <thomas.kovatchitch@gmail.com>
|
||||
Thomas Leonard <thomas.leonard@docker.com>
|
||||
Thomas Léveil <thomasleveil@gmail.com>
|
||||
Thomas Riccardi <riccardi@systran.fr>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type fakeClient struct {
|
||||
client.Client
|
||||
pluginCreateFunc func(createContext io.Reader, createOptions types.PluginCreateOptions) error
|
||||
pluginDisableFunc func(name string, disableOptions types.PluginDisableOptions) error
|
||||
pluginEnableFunc func(name string, options types.PluginEnableOptions) error
|
||||
pluginRemoveFunc func(name string, options types.PluginRemoveOptions) error
|
||||
}
|
||||
|
||||
func (c *fakeClient) PluginCreate(ctx context.Context, createContext io.Reader, createOptions types.PluginCreateOptions) error {
|
||||
if c.pluginCreateFunc != nil {
|
||||
return c.pluginCreateFunc(createContext, createOptions)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeClient) PluginEnable(ctx context.Context, name string, enableOptions types.PluginEnableOptions) error {
|
||||
if c.pluginEnableFunc != nil {
|
||||
return c.pluginEnableFunc(name, enableOptions)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeClient) PluginDisable(context context.Context, name string, disableOptions types.PluginDisableOptions) error {
|
||||
if c.pluginDisableFunc != nil {
|
||||
return c.pluginDisableFunc(name, disableOptions)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeClient) PluginRemove(context context.Context, name string, removeOptions types.PluginRemoveOptions) error {
|
||||
if c.pluginRemoveFunc != nil {
|
||||
return c.pluginRemoveFunc(name, removeOptions)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/internal/test/testutil"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/gotestyourself/gotestyourself/fs"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCreateErrors(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
args []string
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
args: []string{},
|
||||
expectedError: "requires at least 2 arguments",
|
||||
},
|
||||
{
|
||||
args: []string{"INVALID_TAG", "context-dir"},
|
||||
expectedError: "invalid",
|
||||
},
|
||||
{
|
||||
args: []string{"plugin-foo", "nonexistent_context_dir"},
|
||||
expectedError: "no such file or directory",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
cmd := newCreateCommand(cli)
|
||||
cmd.SetArgs(tc.args)
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateErrorOnFileAsContextDir(t *testing.T) {
|
||||
tmpFile := fs.NewFile(t, "file-as-context-dir")
|
||||
defer tmpFile.Remove()
|
||||
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
cmd := newCreateCommand(cli)
|
||||
cmd.SetArgs([]string{"plugin-foo", tmpFile.Path()})
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
testutil.ErrorContains(t, cmd.Execute(), "context must be a directory")
|
||||
}
|
||||
|
||||
func TestCreateErrorOnContextDirWithoutConfig(t *testing.T) {
|
||||
tmpDir := fs.NewDir(t, "plugin-create-test")
|
||||
defer tmpDir.Remove()
|
||||
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
cmd := newCreateCommand(cli)
|
||||
cmd.SetArgs([]string{"plugin-foo", tmpDir.Path()})
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
testutil.ErrorContains(t, cmd.Execute(), "config.json: no such file or directory")
|
||||
}
|
||||
|
||||
func TestCreateErrorOnInvalidConfig(t *testing.T) {
|
||||
tmpDir := fs.NewDir(t, "plugin-create-test",
|
||||
fs.WithDir("rootfs"),
|
||||
fs.WithFile("config.json", "invalid-config-contents"))
|
||||
defer tmpDir.Remove()
|
||||
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
cmd := newCreateCommand(cli)
|
||||
cmd.SetArgs([]string{"plugin-foo", tmpDir.Path()})
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
testutil.ErrorContains(t, cmd.Execute(), "invalid")
|
||||
}
|
||||
|
||||
func TestCreateErrorFromDaemon(t *testing.T) {
|
||||
tmpDir := fs.NewDir(t, "plugin-create-test",
|
||||
fs.WithDir("rootfs"),
|
||||
fs.WithFile("config.json", `{ "Name": "plugin-foo" }`))
|
||||
defer tmpDir.Remove()
|
||||
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
pluginCreateFunc: func(createContext io.Reader, createOptions types.PluginCreateOptions) error {
|
||||
return fmt.Errorf("Error creating plugin")
|
||||
},
|
||||
})
|
||||
|
||||
cmd := newCreateCommand(cli)
|
||||
cmd.SetArgs([]string{"plugin-foo", tmpDir.Path()})
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
testutil.ErrorContains(t, cmd.Execute(), "Error creating plugin")
|
||||
}
|
||||
|
||||
func TestCreatePlugin(t *testing.T) {
|
||||
tmpDir := fs.NewDir(t, "plugin-create-test",
|
||||
fs.WithDir("rootfs"),
|
||||
fs.WithFile("config.json", `{ "Name": "plugin-foo" }`))
|
||||
defer tmpDir.Remove()
|
||||
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
pluginCreateFunc: func(createContext io.Reader, createOptions types.PluginCreateOptions) error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
cmd := newCreateCommand(cli)
|
||||
cmd.SetArgs([]string{"plugin-foo", tmpDir.Path()})
|
||||
assert.NoError(t, cmd.Execute())
|
||||
assert.Equal(t, "plugin-foo\n", cli.OutBuffer().String())
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/internal/test/testutil"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPluginDisableErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
args []string
|
||||
expectedError string
|
||||
pluginDisableFunc func(name string, disableOptions types.PluginDisableOptions) error
|
||||
}{
|
||||
{
|
||||
args: []string{},
|
||||
expectedError: "requires exactly 1 argument",
|
||||
},
|
||||
{
|
||||
args: []string{"too", "many", "arguments"},
|
||||
expectedError: "requires exactly 1 argument",
|
||||
},
|
||||
{
|
||||
args: []string{"plugin-foo"},
|
||||
expectedError: "Error disabling plugin",
|
||||
pluginDisableFunc: func(name string, disableOptions types.PluginDisableOptions) error {
|
||||
return fmt.Errorf("Error disabling plugin")
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
cmd := newDisableCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
pluginDisableFunc: tc.pluginDisableFunc,
|
||||
}))
|
||||
cmd.SetArgs(tc.args)
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginDisable(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
pluginDisableFunc: func(name string, disableOptions types.PluginDisableOptions) error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
cmd := newDisableCommand(cli)
|
||||
cmd.SetArgs([]string{"plugin-foo"})
|
||||
assert.NoError(t, cmd.Execute())
|
||||
assert.Equal(t, "plugin-foo\n", cli.OutBuffer().String())
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/internal/test/testutil"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPluginEnableErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
args []string
|
||||
flags map[string]string
|
||||
pluginEnableFunc func(name string, options types.PluginEnableOptions) error
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
args: []string{},
|
||||
expectedError: "requires exactly 1 argument",
|
||||
},
|
||||
{
|
||||
args: []string{"too-many", "arguments"},
|
||||
expectedError: "requires exactly 1 argument",
|
||||
},
|
||||
{
|
||||
args: []string{"plugin-foo"},
|
||||
pluginEnableFunc: func(name string, options types.PluginEnableOptions) error {
|
||||
return fmt.Errorf("failed to enable plugin")
|
||||
},
|
||||
expectedError: "failed to enable plugin",
|
||||
},
|
||||
{
|
||||
args: []string{"plugin-foo"},
|
||||
flags: map[string]string{
|
||||
"timeout": "-1",
|
||||
},
|
||||
expectedError: "negative timeout -1 is invalid",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
cmd := newEnableCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
pluginEnableFunc: tc.pluginEnableFunc,
|
||||
}))
|
||||
cmd.SetArgs(tc.args)
|
||||
for key, value := range tc.flags {
|
||||
cmd.Flags().Set(key, value)
|
||||
}
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginEnable(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
pluginEnableFunc: func(name string, options types.PluginEnableOptions) error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
cmd := newEnableCommand(cli)
|
||||
cmd.SetArgs([]string{"plugin-foo"})
|
||||
assert.NoError(t, cmd.Execute())
|
||||
assert.Equal(t, "plugin-foo\n", cli.OutBuffer().String())
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/internal/test/testutil"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRemoveErrors(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
args []string
|
||||
pluginRemoveFunc func(name string, options types.PluginRemoveOptions) error
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
args: []string{},
|
||||
expectedError: "requires at least 1 argument",
|
||||
},
|
||||
{
|
||||
args: []string{"plugin-foo"},
|
||||
pluginRemoveFunc: func(name string, options types.PluginRemoveOptions) error {
|
||||
return fmt.Errorf("Error removing plugin")
|
||||
},
|
||||
expectedError: "Error removing plugin",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
pluginRemoveFunc: tc.pluginRemoveFunc,
|
||||
})
|
||||
cmd := newRemoveCommand(cli)
|
||||
cmd.SetArgs(tc.args)
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
pluginRemoveFunc: func(name string, options types.PluginRemoveOptions) error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
cmd := newRemoveCommand(cli)
|
||||
cmd.SetArgs([]string{"plugin-foo"})
|
||||
assert.NoError(t, cmd.Execute())
|
||||
assert.Equal(t, "plugin-foo\n", cli.OutBuffer().String())
|
||||
}
|
||||
|
||||
func TestRemoveWithForceOption(t *testing.T) {
|
||||
force := false
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
pluginRemoveFunc: func(name string, options types.PluginRemoveOptions) error {
|
||||
force = options.Force
|
||||
return nil
|
||||
},
|
||||
})
|
||||
cmd := newRemoveCommand(cli)
|
||||
cmd.SetArgs([]string{"plugin-foo"})
|
||||
cmd.Flags().Set("force", "true")
|
||||
assert.NoError(t, cmd.Execute())
|
||||
assert.True(t, force)
|
||||
assert.Equal(t, "plugin-foo\n", cli.OutBuffer().String())
|
||||
}
|
||||
@@ -19,7 +19,7 @@ const (
|
||||
// This constant is only used for really old config files when the
|
||||
// URL wasn't saved as part of the config file and it was just
|
||||
// assumed to be this value.
|
||||
defaultIndexserver = "https://index.docker.io/v1/"
|
||||
defaultIndexServer = "https://index.docker.io/v1/"
|
||||
)
|
||||
|
||||
// ConfigFile ~/.docker/config.json file info
|
||||
@@ -87,8 +87,8 @@ func (configFile *ConfigFile) LegacyLoadFromReader(configData io.Reader) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authConfig.ServerAddress = defaultIndexserver
|
||||
configFile.AuthConfigs[defaultIndexserver] = authConfig
|
||||
authConfig.ServerAddress = defaultIndexServer
|
||||
configFile.AuthConfigs[defaultIndexServer] = authConfig
|
||||
} else {
|
||||
for k, authConfig := range configFile.AuthConfigs {
|
||||
authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth)
|
||||
@@ -251,24 +251,29 @@ func decodeAuth(authStr string) (string, string, error) {
|
||||
|
||||
// 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)
|
||||
func (configFile *ConfigFile) GetCredentialsStore(registryHostname string) credentials.Store {
|
||||
if helper := getConfiguredCredentialStore(configFile, registryHostname); helper != "" {
|
||||
return newNativeStore(configFile, helper)
|
||||
}
|
||||
return credentials.NewFileStore(configFile)
|
||||
}
|
||||
|
||||
// var for unit testing.
|
||||
var newNativeStore = func(configFile *ConfigFile, helperSuffix string) credentials.Store {
|
||||
return credentials.NewNativeStore(configFile, helperSuffix)
|
||||
}
|
||||
|
||||
// GetAuthConfig for a repository from the credential store
|
||||
func (configFile *ConfigFile) GetAuthConfig(serverAddress string) (types.AuthConfig, error) {
|
||||
return configFile.GetCredentialsStore(serverAddress).Get(serverAddress)
|
||||
func (configFile *ConfigFile) GetAuthConfig(registryHostname string) (types.AuthConfig, error) {
|
||||
return configFile.GetCredentialsStore(registryHostname).Get(registryHostname)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
func getConfiguredCredentialStore(c *ConfigFile, registryHostname string) string {
|
||||
if c.CredentialHelpers != nil && registryHostname != "" {
|
||||
if helper, exists := c.CredentialHelpers[registryHostname]; exists {
|
||||
return helper
|
||||
}
|
||||
}
|
||||
@@ -285,19 +290,20 @@ func (configFile *ConfigFile) GetAllCredentials() (map[string]types.AuthConfig,
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
// Auth configs from a registry-specific helper should override those from the default store.
|
||||
for registryHostname := range configFile.CredentialHelpers {
|
||||
newAuth, err := configFile.GetAuthConfig(registryHostname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
auths[registryHostname] = newAuth
|
||||
}
|
||||
return auths, nil
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/config/credentials"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -142,7 +143,37 @@ func TestConfigFile(t *testing.T) {
|
||||
assert.Equal(t, configFilename, configFile.Filename)
|
||||
}
|
||||
|
||||
func TestGetAllCredentials(t *testing.T) {
|
||||
type mockNativeStore struct {
|
||||
GetAllCallCount int
|
||||
authConfigs map[string]types.AuthConfig
|
||||
}
|
||||
|
||||
func (c *mockNativeStore) Erase(registryHostname string) error {
|
||||
delete(c.authConfigs, registryHostname)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *mockNativeStore) Get(registryHostname string) (types.AuthConfig, error) {
|
||||
return c.authConfigs[registryHostname], nil
|
||||
}
|
||||
|
||||
func (c *mockNativeStore) GetAll() (map[string]types.AuthConfig, error) {
|
||||
c.GetAllCallCount = c.GetAllCallCount + 1
|
||||
return c.authConfigs, nil
|
||||
}
|
||||
|
||||
func (c *mockNativeStore) Store(authConfig types.AuthConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// make sure it satisfies the interface
|
||||
var _ credentials.Store = (*mockNativeStore)(nil)
|
||||
|
||||
func NewMockNativeStore(authConfigs map[string]types.AuthConfig) credentials.Store {
|
||||
return &mockNativeStore{authConfigs: authConfigs}
|
||||
}
|
||||
|
||||
func TestGetAllCredentialsFileStoreOnly(t *testing.T) {
|
||||
configFile := New("filename")
|
||||
exampleAuth := types.AuthConfig{
|
||||
Username: "user",
|
||||
@@ -157,3 +188,186 @@ func TestGetAllCredentials(t *testing.T) {
|
||||
expected["example.com/foo"] = exampleAuth
|
||||
assert.Equal(t, expected, authConfigs)
|
||||
}
|
||||
|
||||
func TestGetAllCredentialsCredsStore(t *testing.T) {
|
||||
configFile := New("filename")
|
||||
configFile.CredentialsStore = "test_creds_store"
|
||||
testRegistryHostname := "example.com"
|
||||
expectedAuth := types.AuthConfig{
|
||||
Username: "user",
|
||||
Password: "pass",
|
||||
}
|
||||
|
||||
testCredsStore := NewMockNativeStore(map[string]types.AuthConfig{testRegistryHostname: expectedAuth})
|
||||
|
||||
tmpNewNativeStore := newNativeStore
|
||||
defer func() { newNativeStore = tmpNewNativeStore }()
|
||||
newNativeStore = func(configFile *ConfigFile, helperSuffix string) credentials.Store {
|
||||
return testCredsStore
|
||||
}
|
||||
|
||||
authConfigs, err := configFile.GetAllCredentials()
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := make(map[string]types.AuthConfig)
|
||||
expected[testRegistryHostname] = expectedAuth
|
||||
assert.Equal(t, expected, authConfigs)
|
||||
assert.Equal(t, 1, testCredsStore.(*mockNativeStore).GetAllCallCount)
|
||||
}
|
||||
|
||||
func TestGetAllCredentialsCredHelper(t *testing.T) {
|
||||
testCredHelperSuffix := "test_cred_helper"
|
||||
testCredHelperRegistryHostname := "credhelper.com"
|
||||
testExtraCredHelperRegistryHostname := "somethingweird.com"
|
||||
|
||||
unexpectedCredHelperAuth := types.AuthConfig{
|
||||
Username: "file_store_user",
|
||||
Password: "file_store_pass",
|
||||
}
|
||||
expectedCredHelperAuth := types.AuthConfig{
|
||||
Username: "cred_helper_user",
|
||||
Password: "cred_helper_pass",
|
||||
}
|
||||
|
||||
configFile := New("filename")
|
||||
configFile.CredentialHelpers = map[string]string{testCredHelperRegistryHostname: testCredHelperSuffix}
|
||||
|
||||
testCredHelper := NewMockNativeStore(map[string]types.AuthConfig{
|
||||
testCredHelperRegistryHostname: expectedCredHelperAuth,
|
||||
// Add in an extra auth entry which doesn't appear in CredentialHelpers section of the configFile.
|
||||
// This verifies that only explicitly configured registries are being requested from the cred helpers.
|
||||
testExtraCredHelperRegistryHostname: unexpectedCredHelperAuth,
|
||||
})
|
||||
|
||||
tmpNewNativeStore := newNativeStore
|
||||
defer func() { newNativeStore = tmpNewNativeStore }()
|
||||
newNativeStore = func(configFile *ConfigFile, helperSuffix string) credentials.Store {
|
||||
return testCredHelper
|
||||
}
|
||||
|
||||
authConfigs, err := configFile.GetAllCredentials()
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := make(map[string]types.AuthConfig)
|
||||
expected[testCredHelperRegistryHostname] = expectedCredHelperAuth
|
||||
assert.Equal(t, expected, authConfigs)
|
||||
assert.Equal(t, 0, testCredHelper.(*mockNativeStore).GetAllCallCount)
|
||||
}
|
||||
|
||||
func TestGetAllCredentialsFileStoreAndCredHelper(t *testing.T) {
|
||||
testFileStoreRegistryHostname := "example.com"
|
||||
testCredHelperSuffix := "test_cred_helper"
|
||||
testCredHelperRegistryHostname := "credhelper.com"
|
||||
|
||||
expectedFileStoreAuth := types.AuthConfig{
|
||||
Username: "file_store_user",
|
||||
Password: "file_store_pass",
|
||||
}
|
||||
expectedCredHelperAuth := types.AuthConfig{
|
||||
Username: "cred_helper_user",
|
||||
Password: "cred_helper_pass",
|
||||
}
|
||||
|
||||
configFile := New("filename")
|
||||
configFile.CredentialHelpers = map[string]string{testCredHelperRegistryHostname: testCredHelperSuffix}
|
||||
configFile.AuthConfigs[testFileStoreRegistryHostname] = expectedFileStoreAuth
|
||||
|
||||
testCredHelper := NewMockNativeStore(map[string]types.AuthConfig{testCredHelperRegistryHostname: expectedCredHelperAuth})
|
||||
|
||||
newNativeStore = func(configFile *ConfigFile, helperSuffix string) credentials.Store {
|
||||
return testCredHelper
|
||||
}
|
||||
|
||||
tmpNewNativeStore := newNativeStore
|
||||
defer func() { newNativeStore = tmpNewNativeStore }()
|
||||
authConfigs, err := configFile.GetAllCredentials()
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := make(map[string]types.AuthConfig)
|
||||
expected[testFileStoreRegistryHostname] = expectedFileStoreAuth
|
||||
expected[testCredHelperRegistryHostname] = expectedCredHelperAuth
|
||||
assert.Equal(t, expected, authConfigs)
|
||||
assert.Equal(t, 0, testCredHelper.(*mockNativeStore).GetAllCallCount)
|
||||
}
|
||||
|
||||
func TestGetAllCredentialsCredStoreAndCredHelper(t *testing.T) {
|
||||
testCredStoreSuffix := "test_creds_store"
|
||||
testCredStoreRegistryHostname := "credstore.com"
|
||||
testCredHelperSuffix := "test_cred_helper"
|
||||
testCredHelperRegistryHostname := "credhelper.com"
|
||||
|
||||
configFile := New("filename")
|
||||
configFile.CredentialsStore = testCredStoreSuffix
|
||||
configFile.CredentialHelpers = map[string]string{testCredHelperRegistryHostname: testCredHelperSuffix}
|
||||
|
||||
expectedCredStoreAuth := types.AuthConfig{
|
||||
Username: "cred_store_user",
|
||||
Password: "cred_store_pass",
|
||||
}
|
||||
expectedCredHelperAuth := types.AuthConfig{
|
||||
Username: "cred_helper_user",
|
||||
Password: "cred_helper_pass",
|
||||
}
|
||||
|
||||
testCredHelper := NewMockNativeStore(map[string]types.AuthConfig{testCredHelperRegistryHostname: expectedCredHelperAuth})
|
||||
testCredsStore := NewMockNativeStore(map[string]types.AuthConfig{testCredStoreRegistryHostname: expectedCredStoreAuth})
|
||||
|
||||
tmpNewNativeStore := newNativeStore
|
||||
defer func() { newNativeStore = tmpNewNativeStore }()
|
||||
newNativeStore = func(configFile *ConfigFile, helperSuffix string) credentials.Store {
|
||||
if helperSuffix == testCredHelperSuffix {
|
||||
return testCredHelper
|
||||
}
|
||||
return testCredsStore
|
||||
}
|
||||
|
||||
authConfigs, err := configFile.GetAllCredentials()
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := make(map[string]types.AuthConfig)
|
||||
expected[testCredStoreRegistryHostname] = expectedCredStoreAuth
|
||||
expected[testCredHelperRegistryHostname] = expectedCredHelperAuth
|
||||
assert.Equal(t, expected, authConfigs)
|
||||
assert.Equal(t, 1, testCredsStore.(*mockNativeStore).GetAllCallCount)
|
||||
assert.Equal(t, 0, testCredHelper.(*mockNativeStore).GetAllCallCount)
|
||||
}
|
||||
|
||||
func TestGetAllCredentialsCredHelperOverridesDefaultStore(t *testing.T) {
|
||||
testCredStoreSuffix := "test_creds_store"
|
||||
testCredHelperSuffix := "test_cred_helper"
|
||||
testRegistryHostname := "example.com"
|
||||
|
||||
configFile := New("filename")
|
||||
configFile.CredentialsStore = testCredStoreSuffix
|
||||
configFile.CredentialHelpers = map[string]string{testRegistryHostname: testCredHelperSuffix}
|
||||
|
||||
unexpectedCredStoreAuth := types.AuthConfig{
|
||||
Username: "cred_store_user",
|
||||
Password: "cred_store_pass",
|
||||
}
|
||||
expectedCredHelperAuth := types.AuthConfig{
|
||||
Username: "cred_helper_user",
|
||||
Password: "cred_helper_pass",
|
||||
}
|
||||
|
||||
testCredHelper := NewMockNativeStore(map[string]types.AuthConfig{testRegistryHostname: expectedCredHelperAuth})
|
||||
testCredsStore := NewMockNativeStore(map[string]types.AuthConfig{testRegistryHostname: unexpectedCredStoreAuth})
|
||||
|
||||
tmpNewNativeStore := newNativeStore
|
||||
defer func() { newNativeStore = tmpNewNativeStore }()
|
||||
newNativeStore = func(configFile *ConfigFile, helperSuffix string) credentials.Store {
|
||||
if helperSuffix == testCredHelperSuffix {
|
||||
return testCredHelper
|
||||
}
|
||||
return testCredsStore
|
||||
}
|
||||
|
||||
authConfigs, err := configFile.GetAllCredentials()
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := make(map[string]types.AuthConfig)
|
||||
expected[testRegistryHostname] = expectedCredHelperAuth
|
||||
assert.Equal(t, expected, authConfigs)
|
||||
assert.Equal(t, 1, testCredsStore.(*mockNativeStore).GetAllCallCount)
|
||||
assert.Equal(t, 0, testCredHelper.(*mockNativeStore).GetAllCallCount)
|
||||
}
|
||||
|
||||
@@ -450,9 +450,9 @@ __docker_complete_events_filter() {
|
||||
;;
|
||||
(event)
|
||||
local -a event_opts
|
||||
event_opts=('attach' 'commit' 'connect' 'copy' 'create' 'delete' 'destroy' 'detach' 'die' 'disconnect' 'exec_create' 'exec_detach'
|
||||
'exec_start' 'export' 'health_status' 'import' 'kill' 'load' 'mount' 'oom' 'pause' 'pull' 'push' 'reload' 'rename' 'resize' 'restart' 'save' 'start'
|
||||
'stop' 'tag' 'top' 'unmount' 'unpause' 'untag' 'update')
|
||||
event_opts=('attach' 'commit' 'connect' 'copy' 'create' 'delete' 'destroy' 'detach' 'die' 'disable' 'disconnect' 'enable' 'exec_create' 'exec_detach'
|
||||
'exec_start' 'export' 'health_status' 'import' 'install' 'kill' 'load' 'mount' 'oom' 'pause' 'pull' 'push' 'reload' 'remove' 'rename' 'resize'
|
||||
'restart' 'save' 'start' 'stop' 'tag' 'top' 'unmount' 'unpause' 'untag' 'update')
|
||||
_describe -t event-filter-opts "event filter options" event_opts && ret=0
|
||||
;;
|
||||
(image)
|
||||
|
||||
@@ -241,10 +241,10 @@ func parseMDContent(mdString string) (description string, examples string) {
|
||||
parsedContent := strings.Split(mdString, "\n## ")
|
||||
for _, s := range parsedContent {
|
||||
if strings.Index(s, "Description") == 0 {
|
||||
description = strings.Trim(s, "Description\n")
|
||||
description = strings.TrimSpace(strings.TrimPrefix(s, "Description"))
|
||||
}
|
||||
if strings.Index(s, "Examples") == 0 {
|
||||
examples = strings.Trim(s, "Examples\n")
|
||||
examples = strings.TrimSpace(strings.TrimPrefix(s, "Examples"))
|
||||
}
|
||||
}
|
||||
return description, examples
|
||||
|
||||
Reference in New Issue
Block a user