From 9d682bb06afdb0e604d5b395336ba4cf471296a8 Mon Sep 17 00:00:00 2001 From: Sylvia Lei Date: Wed, 31 May 2023 17:12:17 +0800 Subject: [PATCH 1/3] refactor fallback store Signed-off-by: Sylvia Lei --- native_store.go | 16 ++ native_store_test.go | 9 + store.go | 111 +++++++---- store_test.go | 428 +++++++++++++++++++++++++++++++++++++++---- 4 files changed, 492 insertions(+), 72 deletions(-) diff --git a/native_store.go b/native_store.go index 94dbd85..d67bbc1 100644 --- a/native_store.go +++ b/native_store.go @@ -63,6 +63,22 @@ func NewNativeStore(helperSuffix string) Store { } } +// NewDefaultNativeStore returns a native store based on the platform-default +// docker credentials helper and a bool indicating if the native store is +// available. +// - Windows: "wincred" +// - Linux: "pass" or "secretservice" +// - macOS: "osxkeychain" +// +// Reference: +// - https://docs.docker.com/engine/reference/commandline/login/#credentials-store +func NewDefaultNativeStore() (Store, bool) { + if helper := getDefaultHelperSuffix(); helper != "" { + return NewNativeStore(helper), true + } + return nil, false +} + // Get retrieves credentials from the store for the given server. func (ns *nativeStore) Get(ctx context.Context, serverAddress string) (auth.Credential, error) { var cred auth.Credential diff --git a/native_store_test.go b/native_store_test.go index 2357043..b2e2b25 100644 --- a/native_store_test.go +++ b/native_store_test.go @@ -176,3 +176,12 @@ func TestNativeStore_errorHandling(t *testing.T) { t.Fatalf("should not get error when no credentials are found") } } + +func TestNewDefaultNativeStore(t *testing.T) { + defaultHelper := getDefaultHelperSuffix() + wantOK := (defaultHelper != "") + + if _, ok := NewDefaultNativeStore(); ok != wantOK { + t.Errorf("NewDefaultNativeStore() = %v, want %v", ok, wantOK) + } +} diff --git a/store.go b/store.go index 2ba9e1b..979a4ff 100644 --- a/store.go +++ b/store.go @@ -17,6 +17,7 @@ package credentials import ( "context" + "errors" "fmt" "os" "path/filepath" @@ -42,9 +43,9 @@ type Store interface { Delete(ctx context.Context, serverAddress string) error } -// dynamicStore dynamically determines which store to use based on the settings +// DynamicStore dynamically determines which store to use based on the settings // in the config file. -type dynamicStore struct { +type DynamicStore struct { config *config.Config options StoreOptions detectedCredsStore string @@ -60,36 +61,45 @@ type StoreOptions struct { // - If AllowPlaintextPut is set to true, Put() will save credentials in // plaintext in the config file when native store is not available. AllowPlaintextPut bool + + // DetectDefaultCredsStore enables detecting the platform-default + // credentials store when the config file has no authentication information. + // + // If DetectDefaultCredsStore is set to true, the store will detect and set + // the default credentials store in the "credsStore" field of the config + // file. + // - Windows: "wincred" + // - Linux: "pass" or "secretservice" + // - macOS: "osxkeychain" + // + // References: + // - https://docs.docker.com/engine/reference/commandline/login/#credentials-store + // - https://docs.docker.com/engine/reference/commandline/cli/#docker-cli-configuration-file-configjson-properties + DetectDefaultCredsStore bool } // NewStore returns a Store based on the given configuration file. // -// For Get(), Put() and Delete(), the returned Store will dynamically determine which underlying credentials -// store to use for the given server address. +// For Get(), Put() and Delete(), the returned Store will dynamically determine +// which underlying credentials store to use for the given server address. // The underlying credentials store is determined in the following order: // 1. Native server-specific credential helper // 2. Native credentials store // 3. The plain-text config file itself // -// If the config file has no authentication information, a platform-default -// native store will be used. -// - Windows: "wincred" -// - Linux: "pass" or "secretservice" -// - macOS: "osxkeychain" -// // References: // - https://docs.docker.com/engine/reference/commandline/login/#credentials-store // - https://docs.docker.com/engine/reference/commandline/cli/#docker-cli-configuration-file-configjson-properties -func NewStore(configPath string, opts StoreOptions) (Store, error) { +func NewStore(configPath string, opts StoreOptions) (*DynamicStore, error) { cfg, err := config.Load(configPath) if err != nil { return nil, err } - ds := &dynamicStore{ + ds := &DynamicStore{ config: cfg, options: opts, } - if !cfg.IsAuthConfigured() { + if opts.DetectDefaultCredsStore && !cfg.IsAuthConfigured() { // no authentication configured, detect the default credentials store ds.detectedCredsStore = getDefaultHelperSuffix() } @@ -106,7 +116,7 @@ func NewStore(configPath string, opts StoreOptions) (Store, error) { // References: // - https://docs.docker.com/engine/reference/commandline/cli/#configuration-files // - https://docs.docker.com/engine/reference/commandline/cli/#change-the-docker-directory -func NewStoreFromDocker(opt StoreOptions) (Store, error) { +func NewStoreFromDocker(opt StoreOptions) (*DynamicStore, error) { configPath, err := getDockerConfigPath() if err != nil { return nil, err @@ -115,14 +125,14 @@ func NewStoreFromDocker(opt StoreOptions) (Store, error) { } // Get retrieves credentials from the store for the given server address. -func (ds *dynamicStore) Get(ctx context.Context, serverAddress string) (auth.Credential, error) { +func (ds *DynamicStore) Get(ctx context.Context, serverAddress string) (auth.Credential, error) { return ds.getStore(serverAddress).Get(ctx, serverAddress) } // Put saves credentials into the store for the given server address. -// Returns ErrPlaintextPutDisabled if native store is not available and -// StoreOptions.AllowPlaintextPut is set to false. -func (ds *dynamicStore) Put(ctx context.Context, serverAddress string, cred auth.Credential) (returnErr error) { +// Put returns ErrPlaintextPutDisabled if native store is not available and +// [StoreOptions].AllowPlaintextPut is set to false. +func (ds *DynamicStore) Put(ctx context.Context, serverAddress string, cred auth.Credential) (returnErr error) { if err := ds.getStore(serverAddress).Put(ctx, serverAddress, cred); err != nil { return err } @@ -138,13 +148,24 @@ func (ds *dynamicStore) Put(ctx context.Context, serverAddress string, cred auth } // Delete removes credentials from the store for the given server address. -func (ds *dynamicStore) Delete(ctx context.Context, serverAddress string) error { +func (ds *DynamicStore) Delete(ctx context.Context, serverAddress string) error { return ds.getStore(serverAddress).Delete(ctx, serverAddress) } +// IsAuthConfigured returns whether there is authentication configured in the +// config file or not. +// +// IsAuthConfigured returns true when: +// - The "credsStore" field is not empty +// - Or the "credHelpers" field is not empty +// - Or there is any entry in the "auths" field +func (ds *DynamicStore) IsAuthConfigured() bool { + return ds.config.IsAuthConfigured() +} + // getHelperSuffix returns the credential helper suffix for the given server // address. -func (ds *dynamicStore) getHelperSuffix(serverAddress string) string { +func (ds *DynamicStore) getHelperSuffix(serverAddress string) string { // 1. Look for a server-specific credential helper first if helper := ds.config.GetCredentialHelper(serverAddress); helper != "" { return helper @@ -158,7 +179,7 @@ func (ds *dynamicStore) getHelperSuffix(serverAddress string) string { } // getStore returns a store for the given server address. -func (ds *dynamicStore) getStore(serverAddress string) Store { +func (ds *DynamicStore) getStore(serverAddress string) Store { if helper := ds.getHelperSuffix(serverAddress); helper != "" { return NewNativeStore(helper) } @@ -189,11 +210,14 @@ type storeWithFallbacks struct { } // NewStoreWithFallbacks returns a new store based on the given stores. -// - Get() searches the primary and the fallback stores -// for the credentials and returns when it finds the -// credentials in any of the stores. -// - Put() saves the credentials into the primary store. -// - Delete() deletes the credentials from the primary store. +// - Get() searches the primary and the fallback stores for the credentials +// and returns when it finds the credentials in any of the stores. +// - Put() attempts to save the credentials into the primary store. If it +// encounters [ErrPlaintextPutDisabled], it will attempt to save the +// credentials into the fallback stores one by one, until it succeeds or +// encounters other errors than [ErrPlaintextPutDisabled]. +// - Delete() removes the credentials from the primary store and all the +// fallback stores. func NewStoreWithFallbacks(primary Store, fallbacks ...Store) Store { if len(fallbacks) == 0 { return primary @@ -203,9 +227,8 @@ func NewStoreWithFallbacks(primary Store, fallbacks ...Store) Store { } } -// Get retrieves credentials from the StoreWithFallbacks for the given server. -// It searches the primary and the fallback stores for the credentials of serverAddress -// and returns when it finds the credentials in any of the stores. +// Get searches the primary and the fallback stores for the credentials of +// serverAddress and returns when it finds the credentials in any of the stores. func (sf *storeWithFallbacks) Get(ctx context.Context, serverAddress string) (auth.Credential, error) { for _, s := range sf.stores { cred, err := s.Get(ctx, serverAddress) @@ -219,14 +242,32 @@ func (sf *storeWithFallbacks) Get(ctx context.Context, serverAddress string) (au return auth.EmptyCredential, nil } -// Put saves credentials into the StoreWithFallbacks. It puts -// the credentials into the primary store. +// Put attempts to save the credentials into the primary store. If it encounters +// ErrPlaintextPutDisabled, it will attempt to save the credentials into the +// fallback stores one by one, until it succeeds or encounters other errors than +// ErrPlaintextPutDisabled. func (sf *storeWithFallbacks) Put(ctx context.Context, serverAddress string, cred auth.Credential) error { - return sf.stores[0].Put(ctx, serverAddress, cred) + var err error + for _, s := range sf.stores { + err = s.Put(ctx, serverAddress, cred) + if err == nil { + return nil + } + if !errors.Is(err, ErrPlaintextPutDisabled) { + return err + } + // fallback to the next store on ErrPlaintextPutDisabled + } + return err } -// Delete removes credentials from the StoreWithFallbacks for the given server. -// It deletes the credentials from the primary store. +// Delete removes the credentials from the primary store and all the fallback +// stores. func (sf *storeWithFallbacks) Delete(ctx context.Context, serverAddress string) error { - return sf.stores[0].Delete(ctx, serverAddress) + for _, s := range sf.stores { + if err := s.Delete(ctx, serverAddress); err != nil { + return err + } + } + return nil } diff --git a/store_test.go b/store_test.go index 53270ae..7e59228 100644 --- a/store_test.go +++ b/store_test.go @@ -28,6 +28,25 @@ import ( "oras.land/oras-go/v2/registry/remote/auth" ) +type badStore struct{} + +var errBadStore = errors.New("bad store!") + +// Get retrieves credentials from the store for the given server address. +func (s *badStore) Get(ctx context.Context, serverAddress string) (auth.Credential, error) { + return auth.EmptyCredential, errBadStore +} + +// Put saves credentials into the store for the given server address. +func (s *badStore) Put(ctx context.Context, serverAddress string, cred auth.Credential) error { + return errBadStore +} + +// Delete removes credentials from the store for the given server address. +func (s *badStore) Delete(ctx context.Context, serverAddress string) error { + return errBadStore +} + func Test_dynamicStore_authConfigured(t *testing.T) { // prepare test content tempDir := t.TempDir() @@ -46,11 +65,17 @@ func Test_dynamicStore_authConfigured(t *testing.T) { t.Fatalf("failed to write config file: %v", err) } - store, err := NewStore(configPath, StoreOptions{AllowPlaintextPut: true}) + ds, err := NewStore(configPath, StoreOptions{AllowPlaintextPut: true}) if err != nil { t.Fatal("NewStore() error =", err) } - ds := store.(*dynamicStore) + + // test IsAuthConfigured + authConfigured := ds.IsAuthConfigured() + if want := true; authConfigured != want { + t.Errorf("dynamicStore.IsAuthConfigured() = %v, want %v", authConfigured, want) + } + serverAddr := "test.example.com" cred := auth.Credential{ Username: "username", @@ -103,11 +128,97 @@ func Test_dynamicStore_noAuthConfigured(t *testing.T) { t.Fatalf("failed to write config file: %v", err) } - store, err := NewStore(configPath, StoreOptions{AllowPlaintextPut: true}) + ds, err := NewStore(configPath, StoreOptions{AllowPlaintextPut: true}) + if err != nil { + t.Fatal("NewStore() error =", err) + } + + // test IsAuthConfigured + authConfigured := ds.IsAuthConfigured() + if want := false; authConfigured != want { + t.Errorf("dynamicStore.IsAuthConfigured() = %v, want %v", authConfigured, want) + } + + serverAddr := "test.example.com" + cred := auth.Credential{ + Username: "username", + Password: "password", + } + ctx := context.Background() + + // Get() should not set detected store back to config + if _, err := ds.Get(ctx, serverAddr); err != nil { + t.Fatal("dynamicStore.Get() error =", err) + } + if got := ds.config.CredentialsStore(); got != "" { + t.Errorf("ds.config.CredentialsStore() = %v, want empty", got) + } + + // test put + if err := ds.Put(ctx, serverAddr, cred); err != nil { + t.Fatal("dynamicStore.Put() error =", err) + } + + // Put() should not set detected store back to config + if got := ds.config.CredentialsStore(); got != "" { + t.Errorf("ds.config.CredentialsStore() = %v, want empty", got) + } + + // test get + got, err := ds.Get(ctx, serverAddr) + if err != nil { + t.Fatal("dynamicStore.Get() error =", err) + } + if want := cred; got != want { + t.Errorf("dynamicStore.Get() = %v, want %v", got, want) + } + + // test delete + err = ds.Delete(ctx, serverAddr) + if err != nil { + t.Fatal("dynamicStore.Delete() error =", err) + } + + // verify delete + got, err = ds.Get(ctx, serverAddr) + if err != nil { + t.Fatal("dynamicStore.Get() error =", err) + } + if want := auth.EmptyCredential; got != want { + t.Errorf("dynamicStore.Get() = %v, want %v", got, want) + } +} + +func Test_dynamicStore_noAuthConfigured_DetectDefaultStore(t *testing.T) { + // prepare test content + tempDir := t.TempDir() + configPath := filepath.Join(tempDir, "no_auth_configured.json") + cfg := configtest.Config{ + SomeConfigField: 123, + } + jsonStr, err := json.Marshal(cfg) + if err != nil { + t.Fatalf("failed to marshal config: %v", err) + } + if err := os.WriteFile(configPath, jsonStr, 0666); err != nil { + t.Fatalf("failed to write config file: %v", err) + } + + opts := StoreOptions{ + AllowPlaintextPut: true, + DetectDefaultCredsStore: true, + } + ds, err := NewStore(configPath, opts) if err != nil { t.Fatal("NewStore() error =", err) } - ds := store.(*dynamicStore) + + // test IsAuthConfigured + authConfigured := ds.IsAuthConfigured() + if want := false; authConfigured != want { + t.Errorf("dynamicStore.IsAuthConfigured() = %v, want %v", authConfigured, want) + } + serverAddr := "test.example.com" cred := auth.Credential{ Username: "username", @@ -274,11 +385,10 @@ func Test_dynamicStore_getHelperSuffix(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - store, err := NewStore(tt.configPath, StoreOptions{}) + ds, err := NewStore(tt.configPath, StoreOptions{}) if err != nil { t.Fatal("NewStore() error =", err) } - ds := store.(*dynamicStore) if got := ds.getHelperSuffix(tt.serverAddress); got != tt.want { t.Errorf("dynamicStore.getHelperSuffix() = %v, want %v", got, tt.want) } @@ -315,11 +425,10 @@ func Test_dynamicStore_getStore_nativeStore(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - store, err := NewStore(tt.configPath, StoreOptions{}) + ds, err := NewStore(tt.configPath, StoreOptions{}) if err != nil { t.Fatal("NewStore() error =", err) } - ds := store.(*dynamicStore) gotStore := ds.getStore(tt.serverAddress) if _, ok := gotStore.(*nativeStore); !ok { t.Errorf("gotStore is not a native store") @@ -347,11 +456,10 @@ func Test_dynamicStore_getStore_fileStore(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - store, err := NewStore(tt.configPath, StoreOptions{}) + ds, err := NewStore(tt.configPath, StoreOptions{}) if err != nil { t.Fatal("NewStore() error =", err) } - ds := store.(*dynamicStore) gotStore := ds.getStore(tt.serverAddress) gotFS1, ok := gotStore.(*FileStore) if !ok { @@ -371,46 +479,292 @@ func Test_dynamicStore_getStore_fileStore(t *testing.T) { } } -func TestStoreWithFallbacks(t *testing.T) { - // Initialize a StoreWithFallbacks +func Test_storeWithFallbacks_Get(t *testing.T) { + // prepare test content + server1 := "foo.registry.com" + cred1 := auth.Credential{ + Username: "username", + Password: "password", + } + server2 := "bar.registry.com" + cred2 := auth.Credential{ + RefreshToken: "identity_token", + } + primaryStore := &testStore{} - firstFallbackStore := &testStore{} - secondFallbackStore := &testStore{} - secondFallbackStore.Put(context.Background(), "localhost:6666", auth.Credential{RefreshToken: "identity_token"}) - sf := NewStoreWithFallbacks(primaryStore, firstFallbackStore, secondFallbackStore) - // Put an entry into the primary store - err := sf.Put(context.Background(), "localhost:2333", auth.Credential{Username: testUsername, Password: testPassword}) + fallbackStore1 := &testStore{ + storage: map[string]auth.Credential{ + server1: cred1, + }, + } + fallbackStore2 := &testStore{ + storage: map[string]auth.Credential{ + server2: cred2, + }, + } + sf := NewStoreWithFallbacks(primaryStore, fallbackStore1, fallbackStore2) + ctx := context.Background() + + // test Get() + got1, err := sf.Get(ctx, server1) if err != nil { - t.Fatal("sf.Put() error =", err) + t.Fatalf("storeWithFallbacks.Get(%s) error = %v", server1, err) } - // Get an entry stored in the primary store - cred, err := sf.Get(context.Background(), "localhost:2333") + if want := cred1; got1 != cred1 { + t.Errorf("storeWithFallbacks.Get(%s) = %v, want %v", server1, got1, want) + } + got2, err := sf.Get(ctx, server2) if err != nil { - t.Fatal("sf.Get() error =", err) + t.Fatalf("storeWithFallbacks.Get(%s) error = %v", server2, err) } - if !reflect.DeepEqual(cred, auth.Credential{Username: testUsername, Password: testPassword}) { - t.Fatal("incorrect credential from the primary store") + if want := cred2; got2 != cred2 { + t.Errorf("storeWithFallbacks.Get(%s) = %v, want %v", server2, got2, want) } - // Get an entry stored in the second fallback store - cred, err = sf.Get(context.Background(), "localhost:6666") + + // test Get(): no credential found + got, err := sf.Get(ctx, "whaterver") if err != nil { - t.Fatal("sf.Get() error =", err) + t.Fatal("storeWithFallbacks.Get() error =", err) + } + if want := auth.EmptyCredential; got != want { + t.Errorf("storeWithFallbacks.Get() = %v, want %v", got, want) + } +} + +func Test_storeWithFallbacks_Get_throwError(t *testing.T) { + badStore := &badStore{} + goodStore := &testStore{} + sf := NewStoreWithFallbacks(badStore, goodStore) + ctx := context.Background() + + // test Get(): should throw error + _, err := sf.Get(ctx, "whatever") + if wantErr := errBadStore; !errors.Is(err, wantErr) { + t.Errorf("storeWithFallback.Get() error = %v, wantErr %v", err, wantErr) } - if !reflect.DeepEqual(cred, auth.Credential{RefreshToken: "identity_token"}) { - t.Fatal("incorrect credential from the second backup store") +} + +func Test_storeWithFallbacks_Put_noFallback(t *testing.T) { + // prepare test content + cfg := configtest.Config{ + SomeConfigField: 123, } - // Delete the entry stored in the primary store - err = sf.Delete(context.Background(), "localhost:2333") + jsonStr, err := json.Marshal(cfg) + if err != nil { + t.Fatalf("failed to marshal config: %v", err) + } + tempDir := t.TempDir() + configPath := filepath.Join(tempDir, "no_auth_configured.json") + if err := os.WriteFile(configPath, jsonStr, 0666); err != nil { + t.Fatalf("failed to write config file: %v", err) + } + opts := StoreOptions{ + AllowPlaintextPut: true, + } + primaryStore, err := NewStore(configPath, opts) // plaintext enabled + if err != nil { + t.Fatalf("NewStore(%s) error = %v", configPath, err) + } + badStore := &badStore{} // bad store + sf := NewStoreWithFallbacks(primaryStore, badStore) + ctx := context.Background() + + server := "example.registry.com" + cred := auth.Credential{ + Username: "username", + Password: "password", + } + // test Put() + if err := sf.Put(ctx, server, cred); err != nil { + t.Fatal("storeWithFallbacks.Put() error =", err) + } + // verify Get() + got, err := sf.Get(ctx, server) + if err != nil { + t.Fatal("storeWithFallbacks.Get() error =", err) + } + if want := cred; got != want { + t.Errorf("storeWithFallbacks.Get() = %v, want %v", got, want) + } +} + +func Test_storeWithFallbacks_Put_fallbackOnErrPlaintextPutDisabled(t *testing.T) { + // prepare test content + cfg := configtest.Config{ + SomeConfigField: 123, + } + jsonStr, err := json.Marshal(cfg) + if err != nil { + t.Fatalf("failed to marshal config: %v", err) + } + tempDir := t.TempDir() + configPath1 := filepath.Join(tempDir, "no_auth_configured_1.json") + if err := os.WriteFile(configPath1, jsonStr, 0666); err != nil { + t.Fatalf("failed to write config file: %v", err) + } + configPath2 := filepath.Join(tempDir, "no_auth_configured_1.json") + if err := os.WriteFile(configPath2, jsonStr, 0666); err != nil { + t.Fatalf("failed to write config file: %v", err) + } + + ds1, err := NewStore(configPath1, StoreOptions{}) // plaintext disabled if err != nil { - t.Fatal("sf.Delete() error =", err) + t.Fatalf("NewStore(%s) error = %v", configPath1, err) + } + ds2, err := NewStore(configPath2, StoreOptions{}) // plaintext disabled + if err != nil { + t.Fatalf("NewStore(%s) error = %v", configPath2, err) + } + fallbackStore := &testStore{} + + sf := NewStoreWithFallbacks(ds1, ds2, fallbackStore) + ctx := context.Background() + + server := "example.registry.com" + cred := auth.Credential{ + Username: "username", + Password: "password", + } + // test Put() + if err := sf.Put(ctx, server, cred); err != nil { + t.Fatal("storeWithFallbacks.Put() error =", err) } - // Check if the entry is deleted - cred, err = sf.Get(context.Background(), "localhost:2333") + // verify Get() + got, err := sf.Get(ctx, server) if err != nil { - t.Fatal("sf.Get() error =", err) + t.Fatal("storeWithFallbacks.Get() error =", err) } - if !reflect.DeepEqual(cred, auth.EmptyCredential) { - t.Fatal("incorrect credential after the delete") + if want := cred; got != want { + t.Errorf("storeWithFallbacks.Get() = %v, want %v", got, want) + } +} + +func Test_storeWithFallbacks_Put_throwErrPlaintextPutDisabled(t *testing.T) { + // prepare test content + cfg := configtest.Config{ + SomeConfigField: 123, + } + jsonStr, err := json.Marshal(cfg) + if err != nil { + t.Fatalf("failed to marshal config: %v", err) + } + tempDir := t.TempDir() + configPath1 := filepath.Join(tempDir, "no_auth_configured_1.json") + if err := os.WriteFile(configPath1, jsonStr, 0666); err != nil { + t.Fatalf("failed to write config file: %v", err) + } + configPath2 := filepath.Join(tempDir, "no_auth_configured_1.json") + if err := os.WriteFile(configPath2, jsonStr, 0666); err != nil { + t.Fatalf("failed to write config file: %v", err) + } + + ds1, err := NewStore(configPath1, StoreOptions{}) // plaintext disabled + if err != nil { + t.Fatalf("NewStore(%s) error = %v", configPath1, err) + } + ds2, err := NewStore(configPath2, StoreOptions{}) // plaintext disabled + if err != nil { + t.Fatalf("NewStore(%s) error = %v", configPath2, err) + } + sf := NewStoreWithFallbacks(ds1, ds2) + ctx := context.Background() + + // test Put() + err = sf.Put(ctx, "whatever", auth.Credential{}) + if wantErr := ErrPlaintextPutDisabled; !errors.Is(err, wantErr) { + t.Errorf("storeWithFallbacks.Put() error = %v, wantErr %v", err, wantErr) + } +} + +func Test_storeWithFallbacks_Put_throwError(t *testing.T) { + badStore := &badStore{} + goodStore := &testStore{} + sf := NewStoreWithFallbacks(badStore, goodStore) + ctx := context.Background() + + // test Put(): should thrown error + err := sf.Put(ctx, "whatever", auth.Credential{}) + if wantErr := errBadStore; !errors.Is(err, wantErr) { + t.Errorf("storeWithFallback.Put() error = %v, wantErr %v", err, wantErr) + } +} + +func Test_storeWithFallbacks_Delete(t *testing.T) { + // prepare test content + server1 := "foo.registry.com" + cred1 := auth.Credential{ + Username: "username", + Password: "password", + } + server2 := "bar.registry.com" + cred2 := auth.Credential{ + RefreshToken: "identity_token", + } + + primaryStore := &testStore{ + storage: map[string]auth.Credential{ + server1: cred1, + server2: cred2, + }, + } + fallbackStore1 := &testStore{ + storage: map[string]auth.Credential{ + server1: cred1, + }, + } + fallbackStore2 := &testStore{ + storage: map[string]auth.Credential{ + server2: cred2, + }, + } + sf := NewStoreWithFallbacks(primaryStore, fallbackStore1, fallbackStore2) + ctx := context.Background() + + // test Delete(): server1 + if err := sf.Delete(ctx, server1); err != nil { + t.Fatal("storeWithFallback.Delete()") + } + // verify primary store + if want := map[string]auth.Credential{server2: cred2}; !reflect.DeepEqual(primaryStore.storage, want) { + t.Errorf("primaryStore.storage = %v, want %v", primaryStore.storage, want) + } + // verify fallback store 1 + if want := map[string]auth.Credential{}; !reflect.DeepEqual(fallbackStore1.storage, want) { + t.Errorf("fallbackStore1.storage = %v, want %v", fallbackStore1.storage, want) + } + // verify fallback store 2 + if want := map[string]auth.Credential{server2: cred2}; !reflect.DeepEqual(fallbackStore2.storage, want) { + t.Errorf("fallbackStore2.storage = %v, want %v", fallbackStore2.storage, want) + } + + // test Delete(): server2 + if err := sf.Delete(ctx, server2); err != nil { + t.Fatal("storeWithFallback.Delete()") + } + // verify primary store + if want := map[string]auth.Credential{}; !reflect.DeepEqual(primaryStore.storage, want) { + t.Errorf("primaryStore.storage = %v, want %v", primaryStore.storage, want) + } + // verify fallback store 1 + if want := map[string]auth.Credential{}; !reflect.DeepEqual(fallbackStore1.storage, want) { + t.Errorf("fallbackStore1.storage = %v, want %v", fallbackStore1.storage, want) + } + // verify fallback store 2 + if want := map[string]auth.Credential{}; !reflect.DeepEqual(fallbackStore2.storage, want) { + t.Errorf("fallbackStore2.storage = %v, want %v", fallbackStore2.storage, want) + } +} + +func Test_storeWithFallbacks_Delete_throwError(t *testing.T) { + badStore := &badStore{} + goodStore := &testStore{} + sf := NewStoreWithFallbacks(badStore, goodStore) + ctx := context.Background() + + // test Delete(): should throw error + err := sf.Delete(ctx, "whatever") + if wantErr := errBadStore; !errors.Is(err, wantErr) { + t.Errorf("storeWithFallback.Delete() error = %v, wantErr %v", err, wantErr) } } From 100a12993c39f3da9456fbd94e13e8e483074b73 Mon Sep 17 00:00:00 2001 From: Sylvia Lei Date: Fri, 9 Jun 2023 15:41:21 +0800 Subject: [PATCH 2/3] revert changes on storeWithFallbacks Signed-off-by: Sylvia Lei --- store.go | 49 ++++++--------------- store_test.go | 119 ++------------------------------------------------ 2 files changed, 17 insertions(+), 151 deletions(-) diff --git a/store.go b/store.go index 979a4ff..5f4d972 100644 --- a/store.go +++ b/store.go @@ -17,7 +17,6 @@ package credentials import ( "context" - "errors" "fmt" "os" "path/filepath" @@ -210,14 +209,11 @@ type storeWithFallbacks struct { } // NewStoreWithFallbacks returns a new store based on the given stores. -// - Get() searches the primary and the fallback stores for the credentials -// and returns when it finds the credentials in any of the stores. -// - Put() attempts to save the credentials into the primary store. If it -// encounters [ErrPlaintextPutDisabled], it will attempt to save the -// credentials into the fallback stores one by one, until it succeeds or -// encounters other errors than [ErrPlaintextPutDisabled]. -// - Delete() removes the credentials from the primary store and all the -// fallback stores. +// - Get() searches the primary and the fallback stores +// for the credentials and returns when it finds the +// credentials in any of the stores. +// - Put() saves the credentials into the primary store. +// - Delete() deletes the credentials from the primary store. func NewStoreWithFallbacks(primary Store, fallbacks ...Store) Store { if len(fallbacks) == 0 { return primary @@ -227,8 +223,9 @@ func NewStoreWithFallbacks(primary Store, fallbacks ...Store) Store { } } -// Get searches the primary and the fallback stores for the credentials of -// serverAddress and returns when it finds the credentials in any of the stores. +// Get retrieves credentials from the StoreWithFallbacks for the given server. +// It searches the primary and the fallback stores for the credentials of serverAddress +// and returns when it finds the credentials in any of the stores. func (sf *storeWithFallbacks) Get(ctx context.Context, serverAddress string) (auth.Credential, error) { for _, s := range sf.stores { cred, err := s.Get(ctx, serverAddress) @@ -242,32 +239,14 @@ func (sf *storeWithFallbacks) Get(ctx context.Context, serverAddress string) (au return auth.EmptyCredential, nil } -// Put attempts to save the credentials into the primary store. If it encounters -// ErrPlaintextPutDisabled, it will attempt to save the credentials into the -// fallback stores one by one, until it succeeds or encounters other errors than -// ErrPlaintextPutDisabled. +// Put saves credentials into the StoreWithFallbacks. It puts +// the credentials into the primary store. func (sf *storeWithFallbacks) Put(ctx context.Context, serverAddress string, cred auth.Credential) error { - var err error - for _, s := range sf.stores { - err = s.Put(ctx, serverAddress, cred) - if err == nil { - return nil - } - if !errors.Is(err, ErrPlaintextPutDisabled) { - return err - } - // fallback to the next store on ErrPlaintextPutDisabled - } - return err + return sf.stores[0].Put(ctx, serverAddress, cred) } -// Delete removes the credentials from the primary store and all the fallback -// stores. +// Delete removes credentials from the StoreWithFallbacks for the given server. +// It deletes the credentials from the primary store. func (sf *storeWithFallbacks) Delete(ctx context.Context, serverAddress string) error { - for _, s := range sf.stores { - if err := s.Delete(ctx, serverAddress); err != nil { - return err - } - } - return nil + return sf.stores[0].Delete(ctx, serverAddress) } diff --git a/store_test.go b/store_test.go index 7e59228..cbfb39f 100644 --- a/store_test.go +++ b/store_test.go @@ -544,7 +544,7 @@ func Test_storeWithFallbacks_Get_throwError(t *testing.T) { } } -func Test_storeWithFallbacks_Put_noFallback(t *testing.T) { +func Test_storeWithFallbacks_Put(t *testing.T) { // prepare test content cfg := configtest.Config{ SomeConfigField: 123, @@ -588,94 +588,6 @@ func Test_storeWithFallbacks_Put_noFallback(t *testing.T) { } } -func Test_storeWithFallbacks_Put_fallbackOnErrPlaintextPutDisabled(t *testing.T) { - // prepare test content - cfg := configtest.Config{ - SomeConfigField: 123, - } - jsonStr, err := json.Marshal(cfg) - if err != nil { - t.Fatalf("failed to marshal config: %v", err) - } - tempDir := t.TempDir() - configPath1 := filepath.Join(tempDir, "no_auth_configured_1.json") - if err := os.WriteFile(configPath1, jsonStr, 0666); err != nil { - t.Fatalf("failed to write config file: %v", err) - } - configPath2 := filepath.Join(tempDir, "no_auth_configured_1.json") - if err := os.WriteFile(configPath2, jsonStr, 0666); err != nil { - t.Fatalf("failed to write config file: %v", err) - } - - ds1, err := NewStore(configPath1, StoreOptions{}) // plaintext disabled - if err != nil { - t.Fatalf("NewStore(%s) error = %v", configPath1, err) - } - ds2, err := NewStore(configPath2, StoreOptions{}) // plaintext disabled - if err != nil { - t.Fatalf("NewStore(%s) error = %v", configPath2, err) - } - fallbackStore := &testStore{} - - sf := NewStoreWithFallbacks(ds1, ds2, fallbackStore) - ctx := context.Background() - - server := "example.registry.com" - cred := auth.Credential{ - Username: "username", - Password: "password", - } - // test Put() - if err := sf.Put(ctx, server, cred); err != nil { - t.Fatal("storeWithFallbacks.Put() error =", err) - } - // verify Get() - got, err := sf.Get(ctx, server) - if err != nil { - t.Fatal("storeWithFallbacks.Get() error =", err) - } - if want := cred; got != want { - t.Errorf("storeWithFallbacks.Get() = %v, want %v", got, want) - } -} - -func Test_storeWithFallbacks_Put_throwErrPlaintextPutDisabled(t *testing.T) { - // prepare test content - cfg := configtest.Config{ - SomeConfigField: 123, - } - jsonStr, err := json.Marshal(cfg) - if err != nil { - t.Fatalf("failed to marshal config: %v", err) - } - tempDir := t.TempDir() - configPath1 := filepath.Join(tempDir, "no_auth_configured_1.json") - if err := os.WriteFile(configPath1, jsonStr, 0666); err != nil { - t.Fatalf("failed to write config file: %v", err) - } - configPath2 := filepath.Join(tempDir, "no_auth_configured_1.json") - if err := os.WriteFile(configPath2, jsonStr, 0666); err != nil { - t.Fatalf("failed to write config file: %v", err) - } - - ds1, err := NewStore(configPath1, StoreOptions{}) // plaintext disabled - if err != nil { - t.Fatalf("NewStore(%s) error = %v", configPath1, err) - } - ds2, err := NewStore(configPath2, StoreOptions{}) // plaintext disabled - if err != nil { - t.Fatalf("NewStore(%s) error = %v", configPath2, err) - } - sf := NewStoreWithFallbacks(ds1, ds2) - ctx := context.Background() - - // test Put() - err = sf.Put(ctx, "whatever", auth.Credential{}) - if wantErr := ErrPlaintextPutDisabled; !errors.Is(err, wantErr) { - t.Errorf("storeWithFallbacks.Put() error = %v, wantErr %v", err, wantErr) - } -} - func Test_storeWithFallbacks_Put_throwError(t *testing.T) { badStore := &badStore{} goodStore := &testStore{} @@ -707,17 +619,8 @@ func Test_storeWithFallbacks_Delete(t *testing.T) { server2: cred2, }, } - fallbackStore1 := &testStore{ - storage: map[string]auth.Credential{ - server1: cred1, - }, - } - fallbackStore2 := &testStore{ - storage: map[string]auth.Credential{ - server2: cred2, - }, - } - sf := NewStoreWithFallbacks(primaryStore, fallbackStore1, fallbackStore2) + badStore := &badStore{} + sf := NewStoreWithFallbacks(primaryStore, badStore) ctx := context.Background() // test Delete(): server1 @@ -728,14 +631,6 @@ func Test_storeWithFallbacks_Delete(t *testing.T) { if want := map[string]auth.Credential{server2: cred2}; !reflect.DeepEqual(primaryStore.storage, want) { t.Errorf("primaryStore.storage = %v, want %v", primaryStore.storage, want) } - // verify fallback store 1 - if want := map[string]auth.Credential{}; !reflect.DeepEqual(fallbackStore1.storage, want) { - t.Errorf("fallbackStore1.storage = %v, want %v", fallbackStore1.storage, want) - } - // verify fallback store 2 - if want := map[string]auth.Credential{server2: cred2}; !reflect.DeepEqual(fallbackStore2.storage, want) { - t.Errorf("fallbackStore2.storage = %v, want %v", fallbackStore2.storage, want) - } // test Delete(): server2 if err := sf.Delete(ctx, server2); err != nil { @@ -745,14 +640,6 @@ func Test_storeWithFallbacks_Delete(t *testing.T) { if want := map[string]auth.Credential{}; !reflect.DeepEqual(primaryStore.storage, want) { t.Errorf("primaryStore.storage = %v, want %v", primaryStore.storage, want) } - // verify fallback store 1 - if want := map[string]auth.Credential{}; !reflect.DeepEqual(fallbackStore1.storage, want) { - t.Errorf("fallbackStore1.storage = %v, want %v", fallbackStore1.storage, want) - } - // verify fallback store 2 - if want := map[string]auth.Credential{}; !reflect.DeepEqual(fallbackStore2.storage, want) { - t.Errorf("fallbackStore2.storage = %v, want %v", fallbackStore2.storage, want) - } } func Test_storeWithFallbacks_Delete_throwError(t *testing.T) { From c5a5afb08f545bd6efdc88e5d5c04ac6bc9baaf9 Mon Sep 17 00:00:00 2001 From: Sylvia Lei Date: Fri, 9 Jun 2023 16:01:52 +0800 Subject: [PATCH 3/3] improve tests Signed-off-by: Sylvia Lei --- internal/config/config_test.go | 4 +- store_test.go | 212 ++++++++++++++++++++++++++------- 2 files changed, 173 insertions(+), 43 deletions(-) diff --git a/internal/config/config_test.go b/internal/config/config_test.go index c505342..e49a2e7 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -1069,7 +1069,7 @@ func TestConfig_IsAuthConfigured(t *testing.T) { cfg, err := Load(configPath) if err != nil { - t.Fatal("LoadConfigFile() error =", err) + t.Fatal("Load() error =", err) } if got := cfg.IsAuthConfigured(); got != tt.want { t.Errorf("IsAuthConfigured() = %v, want %v", got, tt.want) @@ -1186,7 +1186,7 @@ func TestConfig_saveFile(t *testing.T) { cfg, err := Load(configPath) if err != nil { - t.Fatal("LoadConfigFile() error =", err) + t.Fatal("Load() error =", err) } cfg.credentialsStore = tt.newCfg.CredentialsStore cfg.credentialHelpers = tt.newCfg.CredentialHelpers diff --git a/store_test.go b/store_test.go index cbfb39f..daa42d0 100644 --- a/store_test.go +++ b/store_test.go @@ -47,7 +47,137 @@ func (s *badStore) Delete(ctx context.Context, serverAddress string) error { return errBadStore } -func Test_dynamicStore_authConfigured(t *testing.T) { +func Test_DynamicStore_IsAuthConfigured(t *testing.T) { + tempDir := t.TempDir() + + tests := []struct { + name string + fileName string + shouldCreateFile bool + cfg configtest.Config + want bool + }{ + { + name: "not existing file", + fileName: "config.json", + shouldCreateFile: false, + cfg: configtest.Config{}, + want: false, + }, + { + name: "no auth", + fileName: "config.json", + shouldCreateFile: true, + cfg: configtest.Config{ + SomeConfigField: 123, + }, + want: false, + }, + { + name: "empty auths exist", + fileName: "empty_auths.json", + shouldCreateFile: true, + cfg: configtest.Config{ + AuthConfigs: map[string]configtest.AuthConfig{}, + }, + want: false, + }, + { + name: "auths exist, but no credential", + fileName: "no_cred_auths.json", + shouldCreateFile: true, + cfg: configtest.Config{ + AuthConfigs: map[string]configtest.AuthConfig{ + "test.example.com": {}, + }, + }, + want: true, + }, + { + name: "auths exist", + fileName: "auths.json", + shouldCreateFile: true, + cfg: configtest.Config{ + AuthConfigs: map[string]configtest.AuthConfig{ + "test.example.com": { + Auth: "dXNlcm5hbWU6cGFzc3dvcmQ=", + }, + }, + }, + want: true, + }, + { + name: "credsStore exists", + fileName: "credsStore.json", + shouldCreateFile: true, + cfg: configtest.Config{ + CredentialsStore: "teststore", + }, + want: true, + }, + { + name: "empty credHelpers exist", + fileName: "empty_credsStore.json", + shouldCreateFile: true, + cfg: configtest.Config{ + CredentialHelpers: map[string]string{}, + }, + want: false, + }, + { + name: "credHelpers exist", + fileName: "credsStore.json", + shouldCreateFile: true, + cfg: configtest.Config{ + CredentialHelpers: map[string]string{ + "test.example.com": "testhelper", + }, + }, + want: true, + }, + { + name: "all exist", + fileName: "credsStore.json", + shouldCreateFile: true, + cfg: configtest.Config{ + SomeConfigField: 123, + AuthConfigs: map[string]configtest.AuthConfig{ + "test.example.com": {}, + }, + CredentialsStore: "teststore", + CredentialHelpers: map[string]string{ + "test.example.com": "testhelper", + }, + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // prepare test content + configPath := filepath.Join(tempDir, tt.fileName) + if tt.shouldCreateFile { + jsonStr, err := json.Marshal(tt.cfg) + if err != nil { + t.Fatalf("failed to marshal config: %v", err) + } + if err := os.WriteFile(configPath, jsonStr, 0666); err != nil { + t.Fatalf("failed to write config file: %v", err) + } + } + + ds, err := NewStore(configPath, StoreOptions{}) + if err != nil { + t.Fatal("newStore() error =", err) + } + if got := ds.IsAuthConfigured(); got != tt.want { + t.Errorf("DynamicStore.IsAuthConfigured() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_DynamicStore_authConfigured(t *testing.T) { // prepare test content tempDir := t.TempDir() configPath := filepath.Join(tempDir, "auth_configured.json") @@ -73,7 +203,7 @@ func Test_dynamicStore_authConfigured(t *testing.T) { // test IsAuthConfigured authConfigured := ds.IsAuthConfigured() if want := true; authConfigured != want { - t.Errorf("dynamicStore.IsAuthConfigured() = %v, want %v", authConfigured, want) + t.Errorf("DynamicStore.IsAuthConfigured() = %v, want %v", authConfigured, want) } serverAddr := "test.example.com" @@ -85,35 +215,35 @@ func Test_dynamicStore_authConfigured(t *testing.T) { // test put if err := ds.Put(ctx, serverAddr, cred); err != nil { - t.Fatal("dynamicStore.Get() error =", err) + t.Fatal("DynamicStore.Get() error =", err) } // test get got, err := ds.Get(ctx, serverAddr) if err != nil { - t.Fatal("dynamicStore.Get() error =", err) + t.Fatal("DynamicStore.Get() error =", err) } if want := cred; got != want { - t.Errorf("dynamicStore.Get() = %v, want %v", got, want) + t.Errorf("DynamicStore.Get() = %v, want %v", got, want) } // test delete err = ds.Delete(ctx, serverAddr) if err != nil { - t.Fatal("dynamicStore.Delete() error =", err) + t.Fatal("DynamicStore.Delete() error =", err) } // verify delete got, err = ds.Get(ctx, serverAddr) if err != nil { - t.Fatal("dynamicStore.Get() error =", err) + t.Fatal("DynamicStore.Get() error =", err) } if want := auth.EmptyCredential; got != want { - t.Errorf("dynamicStore.Get() = %v, want %v", got, want) + t.Errorf("DynamicStore.Get() = %v, want %v", got, want) } } -func Test_dynamicStore_noAuthConfigured(t *testing.T) { +func Test_DynamicStore_noAuthConfigured(t *testing.T) { // prepare test content tempDir := t.TempDir() configPath := filepath.Join(tempDir, "no_auth_configured.json") @@ -136,7 +266,7 @@ func Test_dynamicStore_noAuthConfigured(t *testing.T) { // test IsAuthConfigured authConfigured := ds.IsAuthConfigured() if want := false; authConfigured != want { - t.Errorf("dynamicStore.IsAuthConfigured() = %v, want %v", authConfigured, want) + t.Errorf("DynamicStore.IsAuthConfigured() = %v, want %v", authConfigured, want) } serverAddr := "test.example.com" @@ -148,7 +278,7 @@ func Test_dynamicStore_noAuthConfigured(t *testing.T) { // Get() should not set detected store back to config if _, err := ds.Get(ctx, serverAddr); err != nil { - t.Fatal("dynamicStore.Get() error =", err) + t.Fatal("DynamicStore.Get() error =", err) } if got := ds.config.CredentialsStore(); got != "" { t.Errorf("ds.config.CredentialsStore() = %v, want empty", got) @@ -156,7 +286,7 @@ func Test_dynamicStore_noAuthConfigured(t *testing.T) { // test put if err := ds.Put(ctx, serverAddr, cred); err != nil { - t.Fatal("dynamicStore.Put() error =", err) + t.Fatal("DynamicStore.Put() error =", err) } // Put() should not set detected store back to config @@ -167,29 +297,29 @@ func Test_dynamicStore_noAuthConfigured(t *testing.T) { // test get got, err := ds.Get(ctx, serverAddr) if err != nil { - t.Fatal("dynamicStore.Get() error =", err) + t.Fatal("DynamicStore.Get() error =", err) } if want := cred; got != want { - t.Errorf("dynamicStore.Get() = %v, want %v", got, want) + t.Errorf("DynamicStore.Get() = %v, want %v", got, want) } // test delete err = ds.Delete(ctx, serverAddr) if err != nil { - t.Fatal("dynamicStore.Delete() error =", err) + t.Fatal("DynamicStore.Delete() error =", err) } // verify delete got, err = ds.Get(ctx, serverAddr) if err != nil { - t.Fatal("dynamicStore.Get() error =", err) + t.Fatal("DynamicStore.Get() error =", err) } if want := auth.EmptyCredential; got != want { - t.Errorf("dynamicStore.Get() = %v, want %v", got, want) + t.Errorf("DynamicStore.Get() = %v, want %v", got, want) } } -func Test_dynamicStore_noAuthConfigured_DetectDefaultStore(t *testing.T) { +func Test_DynamicStore_noAuthConfigured_DetectDefaultStore(t *testing.T) { // prepare test content tempDir := t.TempDir() configPath := filepath.Join(tempDir, "no_auth_configured.json") @@ -216,7 +346,7 @@ func Test_dynamicStore_noAuthConfigured_DetectDefaultStore(t *testing.T) { // test IsAuthConfigured authConfigured := ds.IsAuthConfigured() if want := false; authConfigured != want { - t.Errorf("dynamicStore.IsAuthConfigured() = %v, want %v", authConfigured, want) + t.Errorf("DynamicStore.IsAuthConfigured() = %v, want %v", authConfigured, want) } serverAddr := "test.example.com" @@ -228,7 +358,7 @@ func Test_dynamicStore_noAuthConfigured_DetectDefaultStore(t *testing.T) { // Get() should not set detected store back to config if _, err := ds.Get(ctx, serverAddr); err != nil { - t.Fatal("dynamicStore.Get() error =", err) + t.Fatal("DynamicStore.Get() error =", err) } if got := ds.config.CredentialsStore(); got != "" { t.Errorf("ds.config.CredentialsStore() = %v, want empty", got) @@ -236,7 +366,7 @@ func Test_dynamicStore_noAuthConfigured_DetectDefaultStore(t *testing.T) { // test put if err := ds.Put(ctx, serverAddr, cred); err != nil { - t.Fatal("dynamicStore.Put() error =", err) + t.Fatal("DynamicStore.Put() error =", err) } // Put() should set detected store back to config @@ -249,29 +379,29 @@ func Test_dynamicStore_noAuthConfigured_DetectDefaultStore(t *testing.T) { // test get got, err := ds.Get(ctx, serverAddr) if err != nil { - t.Fatal("dynamicStore.Get() error =", err) + t.Fatal("DynamicStore.Get() error =", err) } if want := cred; got != want { - t.Errorf("dynamicStore.Get() = %v, want %v", got, want) + t.Errorf("DynamicStore.Get() = %v, want %v", got, want) } // test delete err = ds.Delete(ctx, serverAddr) if err != nil { - t.Fatal("dynamicStore.Delete() error =", err) + t.Fatal("DynamicStore.Delete() error =", err) } // verify delete got, err = ds.Get(ctx, serverAddr) if err != nil { - t.Fatal("dynamicStore.Get() error =", err) + t.Fatal("DynamicStore.Get() error =", err) } if want := auth.EmptyCredential; got != want { - t.Errorf("dynamicStore.Get() = %v, want %v", got, want) + t.Errorf("DynamicStore.Get() = %v, want %v", got, want) } } -func Test_dynamicStore_fileStore_AllowPlainTextPut(t *testing.T) { +func Test_DynamicStore_fileStore_AllowPlainTextPut(t *testing.T) { // prepare test content tempDir := t.TempDir() configPath := filepath.Join(tempDir, "config.json") @@ -303,7 +433,7 @@ func Test_dynamicStore_fileStore_AllowPlainTextPut(t *testing.T) { } err = ds.Put(ctx, serverAddr, cred) if wantErr := ErrPlaintextPutDisabled; !errors.Is(err, wantErr) { - t.Errorf("dynamicStore.Put() error = %v, wantErr %v", err, wantErr) + t.Errorf("DynamicStore.Put() error = %v, wantErr %v", err, wantErr) } // test AllowPlainTextPut = true @@ -312,7 +442,7 @@ func Test_dynamicStore_fileStore_AllowPlainTextPut(t *testing.T) { t.Fatal("NewStore() error =", err) } if err := ds.Put(ctx, serverAddr, cred); err != nil { - t.Error("dynamicStore.Put() error =", err) + t.Error("DynamicStore.Put() error =", err) } // verify config file @@ -339,7 +469,7 @@ func Test_dynamicStore_fileStore_AllowPlainTextPut(t *testing.T) { } } -func Test_dynamicStore_getHelperSuffix(t *testing.T) { +func Test_DynamicStore_getHelperSuffix(t *testing.T) { tests := []struct { name string configPath string @@ -390,13 +520,13 @@ func Test_dynamicStore_getHelperSuffix(t *testing.T) { t.Fatal("NewStore() error =", err) } if got := ds.getHelperSuffix(tt.serverAddress); got != tt.want { - t.Errorf("dynamicStore.getHelperSuffix() = %v, want %v", got, tt.want) + t.Errorf("DynamicStore.getHelperSuffix() = %v, want %v", got, tt.want) } }) } } -func Test_dynamicStore_getStore_nativeStore(t *testing.T) { +func Test_DynamicStore_getStore_nativeStore(t *testing.T) { tests := []struct { name string configPath string @@ -437,7 +567,7 @@ func Test_dynamicStore_getStore_nativeStore(t *testing.T) { } } -func Test_dynamicStore_getStore_fileStore(t *testing.T) { +func Test_DynamicStore_getStore_fileStore(t *testing.T) { tests := []struct { name string configPath string @@ -724,10 +854,10 @@ func TestNewStoreFromDocker(t *testing.T) { // test getting an existing credential got, err := ds.Get(ctx, serverAddr1) if err != nil { - t.Fatal("dynamicStore.Get() error =", err) + t.Fatal("DynamicStore.Get() error =", err) } if want := cred1; got != want { - t.Errorf("dynamicStore.Get() = %v, want %v", got, want) + t.Errorf("DynamicStore.Get() = %v, want %v", got, want) } // test putting a new credential @@ -737,30 +867,30 @@ func TestNewStoreFromDocker(t *testing.T) { Password: "password", } if err := ds.Put(ctx, serverAddr2, cred2); err != nil { - t.Fatal("dynamicStore.Get() error =", err) + t.Fatal("DynamicStore.Get() error =", err) } // test getting the new credential got, err = ds.Get(ctx, serverAddr2) if err != nil { - t.Fatal("dynamicStore.Get() error =", err) + t.Fatal("DynamicStore.Get() error =", err) } if want := cred2; got != want { - t.Errorf("dynamicStore.Get() = %v, want %v", got, want) + t.Errorf("DynamicStore.Get() = %v, want %v", got, want) } // test deleting the old credential err = ds.Delete(ctx, serverAddr1) if err != nil { - t.Fatal("dynamicStore.Delete() error =", err) + t.Fatal("DynamicStore.Delete() error =", err) } // verify delete got, err = ds.Get(ctx, serverAddr1) if err != nil { - t.Fatal("dynamicStore.Get() error =", err) + t.Fatal("DynamicStore.Get() error =", err) } if want := auth.EmptyCredential; got != want { - t.Errorf("dynamicStore.Get() = %v, want %v", got, want) + t.Errorf("DynamicStore.Get() = %v, want %v", got, want) } }