Skip to content
This repository was archived by the owner on Apr 7, 2024. It is now read-only.

Commit b60b11b

Browse files
authored
feat!: add NewDefaultNativeStore and DynamicStore.IsAuthConfigured (#73)
Features: 1. Add `NewDefaultNativeStore()` 2. Export `DynamicStore` and `DynamicStore.IsAuthConfigured` 3. Add a new option `StoreOptions.DetectDefaultCredsStore` **BREAKING CHANGE**: When there is no authentication configured in the config file, `DynamicStore` no longer detects and sets the platform-default docker credentials helper by default. Fixes: #71 --------- Signed-off-by: Sylvia Lei <lixlei@microsoft.com>
1 parent 323e44c commit b60b11b

File tree

5 files changed

+506
-90
lines changed

5 files changed

+506
-90
lines changed

internal/config/config_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -1069,7 +1069,7 @@ func TestConfig_IsAuthConfigured(t *testing.T) {
10691069

10701070
cfg, err := Load(configPath)
10711071
if err != nil {
1072-
t.Fatal("LoadConfigFile() error =", err)
1072+
t.Fatal("Load() error =", err)
10731073
}
10741074
if got := cfg.IsAuthConfigured(); got != tt.want {
10751075
t.Errorf("IsAuthConfigured() = %v, want %v", got, tt.want)
@@ -1186,7 +1186,7 @@ func TestConfig_saveFile(t *testing.T) {
11861186

11871187
cfg, err := Load(configPath)
11881188
if err != nil {
1189-
t.Fatal("LoadConfigFile() error =", err)
1189+
t.Fatal("Load() error =", err)
11901190
}
11911191
cfg.credentialsStore = tt.newCfg.CredentialsStore
11921192
cfg.credentialHelpers = tt.newCfg.CredentialHelpers

native_store.go

+16
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,22 @@ func NewNativeStore(helperSuffix string) Store {
6363
}
6464
}
6565

66+
// NewDefaultNativeStore returns a native store based on the platform-default
67+
// docker credentials helper and a bool indicating if the native store is
68+
// available.
69+
// - Windows: "wincred"
70+
// - Linux: "pass" or "secretservice"
71+
// - macOS: "osxkeychain"
72+
//
73+
// Reference:
74+
// - https://docs.docker.com/engine/reference/commandline/login/#credentials-store
75+
func NewDefaultNativeStore() (Store, bool) {
76+
if helper := getDefaultHelperSuffix(); helper != "" {
77+
return NewNativeStore(helper), true
78+
}
79+
return nil, false
80+
}
81+
6682
// Get retrieves credentials from the store for the given server.
6783
func (ns *nativeStore) Get(ctx context.Context, serverAddress string) (auth.Credential, error) {
6884
var cred auth.Credential

native_store_test.go

+9
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,12 @@ func TestNativeStore_errorHandling(t *testing.T) {
176176
t.Fatalf("should not get error when no credentials are found")
177177
}
178178
}
179+
180+
func TestNewDefaultNativeStore(t *testing.T) {
181+
defaultHelper := getDefaultHelperSuffix()
182+
wantOK := (defaultHelper != "")
183+
184+
if _, ok := NewDefaultNativeStore(); ok != wantOK {
185+
t.Errorf("NewDefaultNativeStore() = %v, want %v", ok, wantOK)
186+
}
187+
}

store.go

+41-21
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ type Store interface {
4242
Delete(ctx context.Context, serverAddress string) error
4343
}
4444

45-
// dynamicStore dynamically determines which store to use based on the settings
45+
// DynamicStore dynamically determines which store to use based on the settings
4646
// in the config file.
47-
type dynamicStore struct {
47+
type DynamicStore struct {
4848
config *config.Config
4949
options StoreOptions
5050
detectedCredsStore string
@@ -60,36 +60,45 @@ type StoreOptions struct {
6060
// - If AllowPlaintextPut is set to true, Put() will save credentials in
6161
// plaintext in the config file when native store is not available.
6262
AllowPlaintextPut bool
63+
64+
// DetectDefaultCredsStore enables detecting the platform-default
65+
// credentials store when the config file has no authentication information.
66+
//
67+
// If DetectDefaultCredsStore is set to true, the store will detect and set
68+
// the default credentials store in the "credsStore" field of the config
69+
// file.
70+
// - Windows: "wincred"
71+
// - Linux: "pass" or "secretservice"
72+
// - macOS: "osxkeychain"
73+
//
74+
// References:
75+
// - https://docs.docker.com/engine/reference/commandline/login/#credentials-store
76+
// - https://docs.docker.com/engine/reference/commandline/cli/#docker-cli-configuration-file-configjson-properties
77+
DetectDefaultCredsStore bool
6378
}
6479

6580
// NewStore returns a Store based on the given configuration file.
6681
//
67-
// For Get(), Put() and Delete(), the returned Store will dynamically determine which underlying credentials
68-
// store to use for the given server address.
82+
// For Get(), Put() and Delete(), the returned Store will dynamically determine
83+
// which underlying credentials store to use for the given server address.
6984
// The underlying credentials store is determined in the following order:
7085
// 1. Native server-specific credential helper
7186
// 2. Native credentials store
7287
// 3. The plain-text config file itself
7388
//
74-
// If the config file has no authentication information, a platform-default
75-
// native store will be used.
76-
// - Windows: "wincred"
77-
// - Linux: "pass" or "secretservice"
78-
// - macOS: "osxkeychain"
79-
//
8089
// References:
8190
// - https://docs.docker.com/engine/reference/commandline/login/#credentials-store
8291
// - https://docs.docker.com/engine/reference/commandline/cli/#docker-cli-configuration-file-configjson-properties
83-
func NewStore(configPath string, opts StoreOptions) (Store, error) {
92+
func NewStore(configPath string, opts StoreOptions) (*DynamicStore, error) {
8493
cfg, err := config.Load(configPath)
8594
if err != nil {
8695
return nil, err
8796
}
88-
ds := &dynamicStore{
97+
ds := &DynamicStore{
8998
config: cfg,
9099
options: opts,
91100
}
92-
if !cfg.IsAuthConfigured() {
101+
if opts.DetectDefaultCredsStore && !cfg.IsAuthConfigured() {
93102
// no authentication configured, detect the default credentials store
94103
ds.detectedCredsStore = getDefaultHelperSuffix()
95104
}
@@ -106,7 +115,7 @@ func NewStore(configPath string, opts StoreOptions) (Store, error) {
106115
// References:
107116
// - https://docs.docker.com/engine/reference/commandline/cli/#configuration-files
108117
// - https://docs.docker.com/engine/reference/commandline/cli/#change-the-docker-directory
109-
func NewStoreFromDocker(opt StoreOptions) (Store, error) {
118+
func NewStoreFromDocker(opt StoreOptions) (*DynamicStore, error) {
110119
configPath, err := getDockerConfigPath()
111120
if err != nil {
112121
return nil, err
@@ -115,14 +124,14 @@ func NewStoreFromDocker(opt StoreOptions) (Store, error) {
115124
}
116125

117126
// Get retrieves credentials from the store for the given server address.
118-
func (ds *dynamicStore) Get(ctx context.Context, serverAddress string) (auth.Credential, error) {
127+
func (ds *DynamicStore) Get(ctx context.Context, serverAddress string) (auth.Credential, error) {
119128
return ds.getStore(serverAddress).Get(ctx, serverAddress)
120129
}
121130

122131
// Put saves credentials into the store for the given server address.
123-
// Returns ErrPlaintextPutDisabled if native store is not available and
124-
// StoreOptions.AllowPlaintextPut is set to false.
125-
func (ds *dynamicStore) Put(ctx context.Context, serverAddress string, cred auth.Credential) (returnErr error) {
132+
// Put returns ErrPlaintextPutDisabled if native store is not available and
133+
// [StoreOptions].AllowPlaintextPut is set to false.
134+
func (ds *DynamicStore) Put(ctx context.Context, serverAddress string, cred auth.Credential) (returnErr error) {
126135
if err := ds.getStore(serverAddress).Put(ctx, serverAddress, cred); err != nil {
127136
return err
128137
}
@@ -138,13 +147,24 @@ func (ds *dynamicStore) Put(ctx context.Context, serverAddress string, cred auth
138147
}
139148

140149
// Delete removes credentials from the store for the given server address.
141-
func (ds *dynamicStore) Delete(ctx context.Context, serverAddress string) error {
150+
func (ds *DynamicStore) Delete(ctx context.Context, serverAddress string) error {
142151
return ds.getStore(serverAddress).Delete(ctx, serverAddress)
143152
}
144153

154+
// IsAuthConfigured returns whether there is authentication configured in the
155+
// config file or not.
156+
//
157+
// IsAuthConfigured returns true when:
158+
// - The "credsStore" field is not empty
159+
// - Or the "credHelpers" field is not empty
160+
// - Or there is any entry in the "auths" field
161+
func (ds *DynamicStore) IsAuthConfigured() bool {
162+
return ds.config.IsAuthConfigured()
163+
}
164+
145165
// getHelperSuffix returns the credential helper suffix for the given server
146166
// address.
147-
func (ds *dynamicStore) getHelperSuffix(serverAddress string) string {
167+
func (ds *DynamicStore) getHelperSuffix(serverAddress string) string {
148168
// 1. Look for a server-specific credential helper first
149169
if helper := ds.config.GetCredentialHelper(serverAddress); helper != "" {
150170
return helper
@@ -158,7 +178,7 @@ func (ds *dynamicStore) getHelperSuffix(serverAddress string) string {
158178
}
159179

160180
// getStore returns a store for the given server address.
161-
func (ds *dynamicStore) getStore(serverAddress string) Store {
181+
func (ds *DynamicStore) getStore(serverAddress string) Store {
162182
if helper := ds.getHelperSuffix(serverAddress); helper != "" {
163183
return NewNativeStore(helper)
164184
}

0 commit comments

Comments
 (0)