2016-12-25 14:31:52 -05:00
|
|
|
package credentials
|
|
|
|
|
|
|
|
import (
|
|
|
|
"testing"
|
|
|
|
|
2017-10-15 15:39:56 -04:00
|
|
|
"github.com/docker/cli/cli/config/types"
|
2020-02-22 12:12:14 -05:00
|
|
|
"gotest.tools/v3/assert"
|
|
|
|
is "gotest.tools/v3/assert/cmp"
|
2016-12-25 14:31:52 -05:00
|
|
|
)
|
|
|
|
|
2017-06-21 16:47:06 -04:00
|
|
|
type fakeStore struct {
|
|
|
|
configs map[string]types.AuthConfig
|
2024-10-22 06:11:36 -04:00
|
|
|
saveFn func(*fakeStore) error
|
2017-06-21 16:47:06 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (f *fakeStore) Save() error {
|
2024-10-22 06:11:36 -04:00
|
|
|
if f.saveFn != nil {
|
|
|
|
// Pass a reference to the fakeStore itself in case saveFn
|
|
|
|
// wants to access it.
|
|
|
|
return f.saveFn(f)
|
|
|
|
}
|
2017-06-21 16:47:06 -04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *fakeStore) GetAuthConfigs() map[string]types.AuthConfig {
|
|
|
|
return f.configs
|
|
|
|
}
|
2016-12-25 14:31:52 -05:00
|
|
|
|
2018-03-26 10:18:32 -04:00
|
|
|
func (f *fakeStore) GetFilename() string {
|
2024-10-22 06:11:36 -04:00
|
|
|
return "no-config.json"
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestFileStoreIdempotent verifies that the config-file isn't updated
|
|
|
|
// if nothing changed.
|
|
|
|
func TestFileStoreIdempotent(t *testing.T) {
|
|
|
|
var saveCount, expectedSaveCount int
|
|
|
|
|
|
|
|
s := NewFileStore(&fakeStore{
|
|
|
|
configs: map[string]types.AuthConfig{},
|
|
|
|
saveFn: func(*fakeStore) error {
|
|
|
|
saveCount++
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
})
|
|
|
|
authOne := types.AuthConfig{
|
|
|
|
Auth: "super_secret_token",
|
|
|
|
Email: "foo@example.com",
|
|
|
|
ServerAddress: "https://example.com",
|
|
|
|
}
|
|
|
|
authTwo := types.AuthConfig{
|
|
|
|
Auth: "also_super_secret_token",
|
|
|
|
Email: "bar@example.com",
|
|
|
|
ServerAddress: "https://other.example.com",
|
|
|
|
}
|
|
|
|
|
|
|
|
expectedSaveCount = 1
|
|
|
|
t.Run("store new credentials", func(t *testing.T) {
|
|
|
|
assert.NilError(t, s.Store(authOne))
|
|
|
|
retrievedAuth, err := s.Get(authOne.ServerAddress)
|
|
|
|
assert.NilError(t, err)
|
|
|
|
assert.Check(t, is.Equal(retrievedAuth, authOne))
|
|
|
|
assert.Check(t, is.Equal(saveCount, expectedSaveCount))
|
|
|
|
})
|
|
|
|
t.Run("store same credentials is a no-op", func(t *testing.T) {
|
|
|
|
assert.NilError(t, s.Store(authOne))
|
|
|
|
retrievedAuth, err := s.Get(authOne.ServerAddress)
|
|
|
|
assert.NilError(t, err)
|
|
|
|
assert.Check(t, is.Equal(retrievedAuth, authOne))
|
|
|
|
assert.Check(t, is.Equal(saveCount, expectedSaveCount), "should not have saved if nothing changed")
|
|
|
|
})
|
|
|
|
t.Run("store other credentials", func(t *testing.T) {
|
|
|
|
expectedSaveCount++
|
|
|
|
assert.NilError(t, s.Store(authTwo))
|
|
|
|
retrievedAuth, err := s.Get(authTwo.ServerAddress)
|
|
|
|
assert.NilError(t, err)
|
|
|
|
assert.Check(t, is.Equal(retrievedAuth, authTwo))
|
|
|
|
assert.Check(t, is.Equal(saveCount, expectedSaveCount))
|
|
|
|
})
|
|
|
|
t.Run("erase credentials", func(t *testing.T) {
|
|
|
|
expectedSaveCount++
|
|
|
|
assert.NilError(t, s.Erase(authOne.ServerAddress))
|
|
|
|
retrievedAuth, err := s.Get(authOne.ServerAddress)
|
|
|
|
assert.NilError(t, err)
|
|
|
|
assert.Check(t, is.Equal(retrievedAuth, types.AuthConfig{}))
|
|
|
|
assert.Check(t, is.Equal(saveCount, expectedSaveCount))
|
|
|
|
})
|
|
|
|
t.Run("erase non-existing credentials is a no-op", func(t *testing.T) {
|
|
|
|
assert.NilError(t, s.Erase(authOne.ServerAddress))
|
|
|
|
retrievedAuth, err := s.Get(authOne.ServerAddress)
|
|
|
|
assert.NilError(t, err)
|
|
|
|
assert.Check(t, is.Equal(retrievedAuth, types.AuthConfig{}))
|
|
|
|
assert.Check(t, is.Equal(saveCount, expectedSaveCount), "should not have saved if nothing changed")
|
|
|
|
})
|
|
|
|
t.Run("erase other credentials", func(t *testing.T) {
|
|
|
|
expectedSaveCount++
|
|
|
|
assert.NilError(t, s.Erase(authTwo.ServerAddress))
|
|
|
|
retrievedAuth, err := s.Get(authTwo.ServerAddress)
|
|
|
|
assert.NilError(t, err)
|
|
|
|
assert.Check(t, is.Equal(retrievedAuth, types.AuthConfig{}))
|
|
|
|
assert.Check(t, is.Equal(saveCount, expectedSaveCount))
|
|
|
|
})
|
2018-03-26 10:18:32 -04:00
|
|
|
}
|
|
|
|
|
2016-12-25 14:31:52 -05:00
|
|
|
func TestFileStoreAddCredentials(t *testing.T) {
|
2024-10-22 04:58:29 -04:00
|
|
|
f := &fakeStore{configs: map[string]types.AuthConfig{}}
|
2016-12-25 14:31:52 -05:00
|
|
|
|
|
|
|
s := NewFileStore(f)
|
2017-06-21 16:47:06 -04:00
|
|
|
auth := types.AuthConfig{
|
2016-12-25 14:31:52 -05:00
|
|
|
Auth: "super_secret_token",
|
|
|
|
Email: "foo@example.com",
|
|
|
|
ServerAddress: "https://example.com",
|
|
|
|
}
|
2017-06-21 16:47:06 -04:00
|
|
|
err := s.Store(auth)
|
2018-03-05 18:53:52 -05:00
|
|
|
assert.NilError(t, err)
|
|
|
|
assert.Check(t, is.Len(f.GetAuthConfigs(), 1))
|
2016-12-25 14:31:52 -05:00
|
|
|
|
2017-06-21 16:47:06 -04:00
|
|
|
actual, ok := f.GetAuthConfigs()["https://example.com"]
|
2018-03-05 18:53:52 -05:00
|
|
|
assert.Check(t, ok)
|
|
|
|
assert.Check(t, is.DeepEqual(auth, actual))
|
2016-12-25 14:31:52 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestFileStoreGet(t *testing.T) {
|
2024-10-22 04:58:29 -04:00
|
|
|
f := &fakeStore{configs: map[string]types.AuthConfig{
|
2016-12-25 14:31:52 -05:00
|
|
|
"https://example.com": {
|
|
|
|
Auth: "super_secret_token",
|
|
|
|
Email: "foo@example.com",
|
|
|
|
ServerAddress: "https://example.com",
|
|
|
|
},
|
2024-10-22 04:58:29 -04:00
|
|
|
}}
|
2016-12-25 14:31:52 -05:00
|
|
|
|
|
|
|
s := NewFileStore(f)
|
|
|
|
a, err := s.Get("https://example.com")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
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 TestFileStoreGetAll(t *testing.T) {
|
|
|
|
s1 := "https://example.com"
|
2021-04-30 03:31:41 -04:00
|
|
|
s2 := "https://example2.example.com"
|
2024-10-22 04:58:29 -04:00
|
|
|
f := &fakeStore{configs: map[string]types.AuthConfig{
|
2016-12-25 14:31:52 -05:00
|
|
|
s1: {
|
|
|
|
Auth: "super_secret_token",
|
|
|
|
Email: "foo@example.com",
|
|
|
|
ServerAddress: "https://example.com",
|
|
|
|
},
|
|
|
|
s2: {
|
|
|
|
Auth: "super_secret_token2",
|
|
|
|
Email: "foo@example2.com",
|
2021-04-30 03:31:41 -04:00
|
|
|
ServerAddress: "https://example2.example.com",
|
2016-12-25 14:31:52 -05:00
|
|
|
},
|
2024-10-22 04:58:29 -04:00
|
|
|
}}
|
2016-12-25 14:31:52 -05:00
|
|
|
|
|
|
|
s := NewFileStore(f)
|
|
|
|
as, err := s.GetAll()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if len(as) != 2 {
|
|
|
|
t.Fatalf("wanted 2, got %d", len(as))
|
|
|
|
}
|
|
|
|
if as[s1].Auth != "super_secret_token" {
|
|
|
|
t.Fatalf("expected auth `super_secret_token`, got %s", as[s1].Auth)
|
|
|
|
}
|
|
|
|
if as[s1].Email != "foo@example.com" {
|
|
|
|
t.Fatalf("expected email `foo@example.com`, got %s", as[s1].Email)
|
|
|
|
}
|
|
|
|
if as[s2].Auth != "super_secret_token2" {
|
|
|
|
t.Fatalf("expected auth `super_secret_token2`, got %s", as[s2].Auth)
|
|
|
|
}
|
|
|
|
if as[s2].Email != "foo@example2.com" {
|
|
|
|
t.Fatalf("expected email `foo@example2.com`, got %s", as[s2].Email)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestFileStoreErase(t *testing.T) {
|
2024-10-22 04:58:29 -04:00
|
|
|
f := &fakeStore{configs: map[string]types.AuthConfig{
|
2016-12-25 14:31:52 -05:00
|
|
|
"https://example.com": {
|
|
|
|
Auth: "super_secret_token",
|
|
|
|
Email: "foo@example.com",
|
|
|
|
ServerAddress: "https://example.com",
|
|
|
|
},
|
2024-10-22 04:58:29 -04:00
|
|
|
}}
|
2016-12-25 14:31:52 -05:00
|
|
|
|
|
|
|
s := NewFileStore(f)
|
|
|
|
err := s.Erase("https://example.com")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// file store never returns errors, check that the auth config is empty
|
|
|
|
a, err := s.Get("https://example.com")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if a.Auth != "" {
|
|
|
|
t.Fatalf("expected empty auth token, got %s", a.Auth)
|
|
|
|
}
|
|
|
|
if a.Email != "" {
|
|
|
|
t.Fatalf("expected empty email, got %s", a.Email)
|
|
|
|
}
|
|
|
|
}
|
2022-02-26 14:10:38 -05:00
|
|
|
|
|
|
|
func TestConvertToHostname(t *testing.T) {
|
|
|
|
tests := []struct{ input, expected string }{
|
2024-06-26 07:21:01 -04:00
|
|
|
{
|
|
|
|
input: "127.0.0.1",
|
|
|
|
expected: "127.0.0.1",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
input: "::1",
|
|
|
|
expected: "::1",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// FIXME(thaJeztah): this should be normalized to "::1" if there's no port (or vice-versa, as long as we're consistent)
|
|
|
|
input: "[::1]",
|
|
|
|
expected: "[::1]",
|
|
|
|
},
|
2022-02-26 14:10:38 -05:00
|
|
|
{
|
|
|
|
input: "example.com",
|
|
|
|
expected: "example.com",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
input: "http://example.com",
|
|
|
|
expected: "example.com",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
input: "https://example.com",
|
|
|
|
expected: "example.com",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
input: "https://example.com/",
|
|
|
|
expected: "example.com",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
input: "https://example.com/v2/",
|
|
|
|
expected: "example.com",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// FIXME(thaJeztah): should ConvertToHostname correctly handle this / fail on this?
|
|
|
|
input: "unix:///var/run/docker.sock",
|
|
|
|
expected: "unix:",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// FIXME(thaJeztah): should ConvertToHostname correctly handle this?
|
|
|
|
input: "ftp://example.com",
|
|
|
|
expected: "example.com",
|
|
|
|
},
|
2024-06-25 18:28:25 -04:00
|
|
|
// should support non-standard port in registry url
|
2024-06-26 07:21:01 -04:00
|
|
|
{
|
|
|
|
input: "127.0.0.1:6556",
|
|
|
|
expected: "127.0.0.1:6556",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// FIXME(thaJeztah): this should be normalized to "[::1]:6556"
|
|
|
|
input: "::1:6556",
|
|
|
|
expected: "::1:6556",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
input: "[::1]:6556",
|
|
|
|
expected: "[::1]:6556",
|
|
|
|
},
|
2024-06-25 18:28:25 -04:00
|
|
|
{
|
|
|
|
input: "example.com:6555",
|
|
|
|
expected: "example.com:6555",
|
|
|
|
},
|
2024-06-26 07:21:01 -04:00
|
|
|
{
|
|
|
|
input: "https://127.0.0.1:6555/v2/",
|
|
|
|
expected: "127.0.0.1:6555",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
input: "https://::1:6555/v2/",
|
|
|
|
expected: "[::1]:6555",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
input: "https://[::1]:6555/v2/",
|
|
|
|
expected: "[::1]:6555",
|
|
|
|
},
|
2024-06-25 18:28:25 -04:00
|
|
|
{
|
|
|
|
input: "http://example.com:6555",
|
|
|
|
expected: "example.com:6555",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
input: "https://example.com:6555",
|
|
|
|
expected: "example.com:6555",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
input: "https://example.com:6555/v2/",
|
|
|
|
expected: "example.com:6555",
|
|
|
|
},
|
2022-02-26 14:10:38 -05:00
|
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
|
|
tc := tc
|
|
|
|
t.Run(tc.input, func(t *testing.T) {
|
|
|
|
actual := ConvertToHostname(tc.input)
|
|
|
|
assert.Equal(t, actual, tc.expected)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|