diff --git a/changelogs/unreleased/5167-lyndon b/changelogs/unreleased/5167-lyndon new file mode 100644 index 0000000000..7bef82dccb --- /dev/null +++ b/changelogs/unreleased/5167-lyndon @@ -0,0 +1 @@ +Add changes for Kopia Integration: Unified Repository Provider - Repo Password \ No newline at end of file diff --git a/internal/credentials/getter.go b/internal/credentials/getter.go new file mode 100644 index 0000000000..c890c05666 --- /dev/null +++ b/internal/credentials/getter.go @@ -0,0 +1,24 @@ +/* +Copyright the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package credentials + +// CredentialGetter is a collection of interfaces for interacting with credentials +// that are stored in different targets +type CredentialGetter struct { + FromFile FileStore + FromSecret SecretStore +} diff --git a/internal/credentials/mocks/FileStore.go b/internal/credentials/mocks/FileStore.go new file mode 100644 index 0000000000..3fce9e843d --- /dev/null +++ b/internal/credentials/mocks/FileStore.go @@ -0,0 +1,49 @@ +// Code generated by mockery v2.14.0. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + v1 "k8s.io/api/core/v1" +) + +// FileStore is an autogenerated mock type for the FileStore type +type FileStore struct { + mock.Mock +} + +// Path provides a mock function with given fields: selector +func (_m *FileStore) Path(selector *v1.SecretKeySelector) (string, error) { + ret := _m.Called(selector) + + var r0 string + if rf, ok := ret.Get(0).(func(*v1.SecretKeySelector) string); ok { + r0 = rf(selector) + } else { + r0 = ret.Get(0).(string) + } + + var r1 error + if rf, ok := ret.Get(1).(func(*v1.SecretKeySelector) error); ok { + r1 = rf(selector) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTNewFileStore interface { + mock.TestingT + Cleanup(func()) +} + +// NewFileStore creates a new instance of FileStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewFileStore(t mockConstructorTestingTNewFileStore) *FileStore { + mock := &FileStore{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/credentials/mocks/SecretStore.go b/internal/credentials/mocks/SecretStore.go new file mode 100644 index 0000000000..5494511c9e --- /dev/null +++ b/internal/credentials/mocks/SecretStore.go @@ -0,0 +1,49 @@ +// Code generated by mockery v2.14.0. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + v1 "k8s.io/api/core/v1" +) + +// SecretStore is an autogenerated mock type for the SecretStore type +type SecretStore struct { + mock.Mock +} + +// Get provides a mock function with given fields: selector +func (_m *SecretStore) Get(selector *v1.SecretKeySelector) (string, error) { + ret := _m.Called(selector) + + var r0 string + if rf, ok := ret.Get(0).(func(*v1.SecretKeySelector) string); ok { + r0 = rf(selector) + } else { + r0 = ret.Get(0).(string) + } + + var r1 error + if rf, ok := ret.Get(1).(func(*v1.SecretKeySelector) error); ok { + r1 = rf(selector) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTNewSecretStore interface { + mock.TestingT + Cleanup(func()) +} + +// NewSecretStore creates a new instance of SecretStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewSecretStore(t mockConstructorTestingTNewSecretStore) *SecretStore { + mock := &SecretStore{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/credentials/secret_store.go b/internal/credentials/secret_store.go new file mode 100644 index 0000000000..f4d2111a57 --- /dev/null +++ b/internal/credentials/secret_store.go @@ -0,0 +1,56 @@ +/* +Copyright the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package credentials + +import ( + "github.com/pkg/errors" + corev1api "k8s.io/api/core/v1" + kbclient "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/vmware-tanzu/velero/pkg/util/kube" +) + +// SecretStore defines operations for interacting with credentials +// that are stored in Secret. +type SecretStore interface { + // Get returns the secret key defined by the given selector + Get(selector *corev1api.SecretKeySelector) (string, error) +} + +type namespacedSecretStore struct { + client kbclient.Client + namespace string +} + +// NewNamespacedSecretStore returns a SecretStore which can interact with credentials +// for the given namespace. +func NewNamespacedSecretStore(client kbclient.Client, namespace string) (SecretStore, error) { + return &namespacedSecretStore{ + client: client, + namespace: namespace, + }, nil +} + +// Buffer returns the secret key defined by the given selector. +func (n *namespacedSecretStore) Get(selector *corev1api.SecretKeySelector) (string, error) { + creds, err := kube.GetSecretKey(n.client, n.namespace, selector) + if err != nil { + return "", errors.Wrap(err, "unable to get key for secret") + } + + return string(creds), nil +} diff --git a/pkg/cmd/server/server.go b/pkg/cmd/server/server.go index f257c96e72..642e1baf5b 100644 --- a/pkg/cmd/server/server.go +++ b/pkg/cmd/server/server.go @@ -80,6 +80,7 @@ import ( "github.com/vmware-tanzu/velero/internal/storage" "github.com/vmware-tanzu/velero/internal/util/managercontroller" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + repokey "github.com/vmware-tanzu/velero/pkg/repository/keys" ) const ( @@ -519,7 +520,7 @@ func (s *server) initRestic() error { } // ensure the repo key secret is set up - if err := restic.EnsureCommonRepositoryKey(s.kubeClient.CoreV1(), s.namespace); err != nil { + if err := repokey.EnsureCommonRepositoryKey(s.kubeClient.CoreV1(), s.namespace); err != nil { return err } diff --git a/pkg/controller/pod_volume_backup_controller.go b/pkg/controller/pod_volume_backup_controller.go index 9cb6a3ae26..9ba5b447ec 100644 --- a/pkg/controller/pod_volume_backup_controller.go +++ b/pkg/controller/pod_volume_backup_controller.go @@ -36,6 +36,7 @@ import ( "github.com/vmware-tanzu/velero/internal/credentials" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/metrics" + repokey "github.com/vmware-tanzu/velero/pkg/repository/keys" "github.com/vmware-tanzu/velero/pkg/restic" "github.com/vmware-tanzu/velero/pkg/util/filesystem" "github.com/vmware-tanzu/velero/pkg/util/kube" @@ -324,7 +325,7 @@ func (r *PodVolumeBackupReconciler) buildResticCommand(ctx context.Context, log log.WithField("path", path).Debugf("Found path matching glob") // Temporary credentials. - details.credsFile, err = r.CredsFileStore.Path(restic.RepoKeySelector()) + details.credsFile, err = r.CredsFileStore.Path(repokey.RepoKeySelector()) if err != nil { return nil, errors.Wrap(err, "creating temporary Restic credentials file") } diff --git a/pkg/controller/pod_volume_restore_controller.go b/pkg/controller/pod_volume_restore_controller.go index 3315dae3ba..e52de6fdbc 100644 --- a/pkg/controller/pod_volume_restore_controller.go +++ b/pkg/controller/pod_volume_restore_controller.go @@ -39,6 +39,7 @@ import ( "github.com/vmware-tanzu/velero/internal/credentials" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + repokey "github.com/vmware-tanzu/velero/pkg/repository/keys" "github.com/vmware-tanzu/velero/pkg/restic" "github.com/vmware-tanzu/velero/pkg/util/boolptr" "github.com/vmware-tanzu/velero/pkg/util/filesystem" @@ -241,7 +242,7 @@ func (c *PodVolumeRestoreReconciler) processRestore(ctx context.Context, req *ve return errors.Wrap(err, "error identifying path of volume") } - credsFile, err := c.credentialsFileStore.Path(restic.RepoKeySelector()) + credsFile, err := c.credentialsFileStore.Path(repokey.RepoKeySelector()) if err != nil { return errors.Wrap(err, "error creating temp restic credentials file") } diff --git a/pkg/repository/keys/keys.go b/pkg/repository/keys/keys.go new file mode 100644 index 0000000000..3da876e28f --- /dev/null +++ b/pkg/repository/keys/keys.go @@ -0,0 +1,75 @@ +/* +Copyright the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package keys + +import ( + "context" + + "github.com/pkg/errors" + corev1api "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + corev1client "k8s.io/client-go/kubernetes/typed/core/v1" + + "github.com/vmware-tanzu/velero/pkg/builder" +) + +const ( + credentialsSecretName = "velero-restic-credentials" + credentialsKey = "repository-password" + + encryptionKey = "static-passw0rd" +) + +func EnsureCommonRepositoryKey(secretClient corev1client.SecretsGetter, namespace string) error { + _, err := secretClient.Secrets(namespace).Get(context.TODO(), credentialsSecretName, metav1.GetOptions{}) + if err != nil && !apierrors.IsNotFound(err) { + return errors.WithStack(err) + } + if err == nil { + return nil + } + + // if we got here, we got an IsNotFound error, so we need to create the key + + secret := &corev1api.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: credentialsSecretName, + }, + Type: corev1api.SecretTypeOpaque, + Data: map[string][]byte{ + credentialsKey: []byte(encryptionKey), + }, + } + + if _, err = secretClient.Secrets(namespace).Create(context.TODO(), secret, metav1.CreateOptions{}); err != nil { + return errors.Wrapf(err, "error creating %s secret", credentialsSecretName) + } + + return nil +} + +// RepoKeySelector returns the SecretKeySelector which can be used to fetch +// the restic repository key. +func RepoKeySelector() *corev1api.SecretKeySelector { + // For now, all restic repos share the same key so we don't need the repoName to fetch it. + // When we move to full-backup encryption, we'll likely have a separate key per restic repo + // (all within the Velero server's namespace) so RepoKeySelector will need to select the key + // for that repo. + return builder.ForSecretKeySelector(credentialsSecretName, credentialsKey).Result() +} diff --git a/pkg/repository/keys/keys_test.go b/pkg/repository/keys/keys_test.go new file mode 100644 index 0000000000..3102b58c85 --- /dev/null +++ b/pkg/repository/keys/keys_test.go @@ -0,0 +1,30 @@ +/* +Copyright the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package keys + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestRepoKeySelector(t *testing.T) { + selector := RepoKeySelector() + + require.Equal(t, credentialsSecretName, selector.Name) + require.Equal(t, credentialsKey, selector.Key) +} diff --git a/pkg/repository/provider/unified_repo.go b/pkg/repository/provider/unified_repo.go index 49cef09ce4..8ddff57999 100644 --- a/pkg/repository/provider/unified_repo.go +++ b/pkg/repository/provider/unified_repo.go @@ -28,15 +28,16 @@ import ( "github.com/vmware-tanzu/velero/internal/credentials" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" repoconfig "github.com/vmware-tanzu/velero/pkg/repository/config" + repokey "github.com/vmware-tanzu/velero/pkg/repository/keys" "github.com/vmware-tanzu/velero/pkg/repository/udmrepo" "github.com/vmware-tanzu/velero/pkg/util/ownership" ) type unifiedRepoProvider struct { - credentialsFileStore credentials.FileStore - workPath string - repoService udmrepo.BackupRepoService - log logrus.FieldLogger + credentialGetter credentials.CredentialGetter + workPath string + repoService udmrepo.BackupRepoService + log logrus.FieldLogger } // this func is assigned to a package-level variable so it can be @@ -47,18 +48,30 @@ var getGCPCredentials = repoconfig.GetGCPCredentials var getS3BucketRegion = repoconfig.GetAWSBucketRegion var getAzureStorageDomain = repoconfig.GetAzureStorageDomain +type localFuncTable struct { + getRepoPassword func(credentials.SecretStore, RepoParam) (string, error) + getStorageVariables func(*velerov1api.BackupStorageLocation, string) (map[string]string, error) + getStorageCredentials func(*velerov1api.BackupStorageLocation, credentials.FileStore) (map[string]string, error) +} + +var funcTable = localFuncTable{ + getRepoPassword: getRepoPassword, + getStorageVariables: getStorageVariables, + getStorageCredentials: getStorageCredentials, +} + // NewUnifiedRepoProvider creates the service provider for Unified Repo // workPath is the path for Unified Repo to store some local information // workPath could be empty, if so, the default path will be used func NewUnifiedRepoProvider( - credentialFileStore credentials.FileStore, + credentialGetter credentials.CredentialGetter, workPath string, log logrus.FieldLogger, ) (Provider, error) { repo := unifiedRepoProvider{ - credentialsFileStore: credentialFileStore, - workPath: workPath, - log: log, + credentialGetter: credentialGetter, + workPath: workPath, + log: log, } repo.repoService = createRepoService(log) @@ -120,15 +133,38 @@ func (urp *unifiedRepoProvider) Forget(ctx context.Context, snapshotID string, p return nil } -func (urp *unifiedRepoProvider) getRepoPassword(param RepoParam) (string, error) { - ///TODO: get repo password +func getRepoPassword(secretStore credentials.SecretStore, param RepoParam) (string, error) { + if secretStore == nil { + return "", errors.New("invalid credentials interface") + } + + buf, err := secretStore.Get(repokey.RepoKeySelector()) + if err != nil { + return "", errors.Wrap(err, "error to get password buffer") + } - return "", nil + return strings.TrimSpace(string(buf)), nil } func (urp *unifiedRepoProvider) getRepoOption(param RepoParam) (udmrepo.RepoOptions, error) { + repoPassword, err := funcTable.getRepoPassword(urp.credentialGetter.FromSecret, param) + if err != nil { + return udmrepo.RepoOptions{}, errors.Wrap(err, "error to get repo password") + } + + storeVar, err := funcTable.getStorageVariables(param.BackupLocation, param.SubDir) + if err != nil { + return udmrepo.RepoOptions{}, errors.Wrap(err, "error to get storage variables") + } + + storeCred, err := funcTable.getStorageCredentials(param.BackupLocation, urp.credentialGetter.FromFile) + if err != nil { + return udmrepo.RepoOptions{}, errors.Wrap(err, "error to get repo credentials") + } + repoOption := udmrepo.RepoOptions{ StorageType: getStorageType(param.BackupLocation), + RepoPassword: repoPassword, ConfigFilePath: getRepoConfigFile(urp.workPath, string(param.BackupLocation.UID)), Ownership: udmrepo.OwnershipOptions{ Username: ownership.GetRepositoryOwner().Username, @@ -138,27 +174,10 @@ func (urp *unifiedRepoProvider) getRepoOption(param RepoParam) (udmrepo.RepoOpti GeneralOptions: make(map[string]string), } - repoPassword, err := urp.getRepoPassword(param) - if err != nil { - return repoOption, errors.Wrap(err, "error to get repo password") - } - - repoOption.RepoPassword = repoPassword - - storeVar, err := getStorageVariables(param.BackupLocation, param.SubDir) - if err != nil { - return repoOption, errors.Wrap(err, "error to get storage variables") - } - for k, v := range storeVar { repoOption.StorageOptions[k] = v } - storeCred, err := getStorageCredentials(param.BackupLocation, urp.credentialsFileStore) - if err != nil { - return repoOption, errors.Wrap(err, "error to get repo credential env") - } - for k, v := range storeCred { repoOption.StorageOptions[k] = v } @@ -187,6 +206,10 @@ func getStorageCredentials(backupLocation *velerov1api.BackupStorageLocation, cr result := make(map[string]string) var err error + if credentialsFileStore == nil { + return map[string]string{}, errors.New("invalid credentials interface") + } + backendType := repoconfig.GetBackendType(backupLocation.Spec.Provider) if !repoconfig.IsBackendTypeValid(backendType) { return map[string]string{}, errors.New("invalid storage provider") diff --git a/pkg/repository/provider/unified_repo_test.go b/pkg/repository/provider/unified_repo_test.go index ee78c7b5d2..f2cccb8e5d 100644 --- a/pkg/repository/provider/unified_repo_test.go +++ b/pkg/repository/provider/unified_repo_test.go @@ -22,25 +22,35 @@ import ( awscredentials "github.com/aws/aws-sdk-go/aws/credentials" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + corev1api "k8s.io/api/core/v1" - filecredentials "github.com/vmware-tanzu/velero/internal/credentials" + velerocredentials "github.com/vmware-tanzu/velero/internal/credentials" + credmock "github.com/vmware-tanzu/velero/internal/credentials/mocks" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" - velerotest "github.com/vmware-tanzu/velero/pkg/test" + "github.com/vmware-tanzu/velero/pkg/repository/udmrepo" ) func TestGetStorageCredentials(t *testing.T) { testCases := []struct { name string backupLocation velerov1api.BackupStorageLocation - credFileStore filecredentials.FileStore + credFileStore *credmock.FileStore + credStoreError error + credStorePath string getAzureCredentials func(map[string]string) (string, string, error) getS3Credentials func(map[string]string) (awscredentials.Value, error) getGCPCredentials func(map[string]string) string expected map[string]string expectedErr string }{ + { + name: "invalid credentials file store interface", + expected: map[string]string{}, + expectedErr: "invalid credentials interface", + }, { name: "invalid provider", backupLocation: velerov1api.BackupStorageLocation{ @@ -48,8 +58,9 @@ func TestGetStorageCredentials(t *testing.T) { Provider: "invalid-provider", }, }, - expected: map[string]string{}, - expectedErr: "invalid storage provider", + credFileStore: new(credmock.FileStore), + expected: map[string]string{}, + expectedErr: "invalid storage provider", }, { name: "credential section exists in BSL, file store fail", @@ -59,9 +70,10 @@ func TestGetStorageCredentials(t *testing.T) { Credential: &corev1api.SecretKeySelector{}, }, }, - credFileStore: velerotest.NewFakeCredentialsFileStore("", errors.New("fake error")), - expected: map[string]string{}, - expectedErr: "error get credential file in bsl: fake error", + credFileStore: new(credmock.FileStore), + credStoreError: errors.New("fake error"), + expected: map[string]string{}, + expectedErr: "error get credential file in bsl: fake error", }, { name: "aws, Credential section not exists in BSL", @@ -78,7 +90,7 @@ func TestGetStorageCredentials(t *testing.T) { AccessKeyID: "from: " + config["credentialsFile"], }, nil }, - + credFileStore: new(credmock.FileStore), expected: map[string]string{ "accessKeyID": "from: credentials-from-config-map", "providerName": "", @@ -97,7 +109,8 @@ func TestGetStorageCredentials(t *testing.T) { Credential: &corev1api.SecretKeySelector{}, }, }, - credFileStore: velerotest.NewFakeCredentialsFileStore("credentials-from-credential-key", nil), + credFileStore: new(credmock.FileStore), + credStorePath: "credentials-from-credential-key", getS3Credentials: func(config map[string]string) (awscredentials.Value, error) { return awscredentials.Value{ AccessKeyID: "from: " + config["credentialsFile"], @@ -121,12 +134,12 @@ func TestGetStorageCredentials(t *testing.T) { }, }, }, - credFileStore: velerotest.NewFakeCredentialsFileStore("", nil), getS3Credentials: func(config map[string]string) (awscredentials.Value, error) { return awscredentials.Value{}, errors.New("fake error") }, - expected: map[string]string{}, - expectedErr: "error get s3 credentials: fake error", + credFileStore: new(credmock.FileStore), + expected: map[string]string{}, + expectedErr: "error get s3 credentials: fake error", }, { name: "azure, Credential section exists in BSL", @@ -139,7 +152,8 @@ func TestGetStorageCredentials(t *testing.T) { Credential: &corev1api.SecretKeySelector{}, }, }, - credFileStore: velerotest.NewFakeCredentialsFileStore("credentials-from-credential-key", nil), + credFileStore: new(credmock.FileStore), + credStorePath: "credentials-from-credential-key", getAzureCredentials: func(config map[string]string) (string, string, error) { return "storage account from: " + config["credentialsFile"], "", nil }, @@ -162,9 +176,9 @@ func TestGetStorageCredentials(t *testing.T) { getAzureCredentials: func(config map[string]string) (string, string, error) { return "", "", errors.New("fake error") }, - - expected: map[string]string{}, - expectedErr: "error get azure credentials: fake error", + credFileStore: new(credmock.FileStore), + expected: map[string]string{}, + expectedErr: "error get azure credentials: fake error", }, { name: "gcp, Credential section not exists in BSL", @@ -179,7 +193,7 @@ func TestGetStorageCredentials(t *testing.T) { getGCPCredentials: func(config map[string]string) string { return "credentials-from-config-map" }, - + credFileStore: new(credmock.FileStore), expected: map[string]string{ "credFile": "credentials-from-config-map", }, @@ -192,7 +206,13 @@ func TestGetStorageCredentials(t *testing.T) { getS3Credentials = tc.getS3Credentials getGCPCredentials = tc.getGCPCredentials - actual, err := getStorageCredentials(&tc.backupLocation, tc.credFileStore) + var fileStore velerocredentials.FileStore + if tc.credFileStore != nil { + tc.credFileStore.On("Path", mock.Anything, mock.Anything).Return(tc.credStorePath, tc.credStoreError) + fileStore = tc.credFileStore + } + + actual, err := getStorageCredentials(&tc.backupLocation, fileStore) require.Equal(t, tc.expected, actual) @@ -412,3 +432,124 @@ func TestGetStorageVariables(t *testing.T) { }) } } + +func TestGetRepoPassword(t *testing.T) { + testCases := []struct { + name string + getter *credmock.SecretStore + credStoreReturn string + credStoreError error + cached string + expected string + expectedErr string + }{ + { + name: "invalid secret interface", + expectedErr: "invalid credentials interface", + }, + { + name: "error from secret interface", + getter: new(credmock.SecretStore), + credStoreError: errors.New("fake error"), + expectedErr: "error to get password buffer: fake error", + }, + { + name: "secret with whitespace", + getter: new(credmock.SecretStore), + credStoreReturn: " fake-passwor d ", + expected: "fake-passwor d", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var secretStore velerocredentials.SecretStore + if tc.getter != nil { + tc.getter.On("Get", mock.Anything, mock.Anything).Return(tc.credStoreReturn, tc.credStoreError) + secretStore = tc.getter + } + + urp := unifiedRepoProvider{ + credentialGetter: velerocredentials.CredentialGetter{ + FromSecret: secretStore, + }, + } + + password, err := getRepoPassword(urp.credentialGetter.FromSecret, RepoParam{}) + + require.Equal(t, tc.expected, password) + + if tc.expectedErr == "" { + assert.NoError(t, err) + } else { + assert.EqualError(t, err, tc.expectedErr) + } + }) + } +} + +func TestGetRepoOption(t *testing.T) { + testCases := []struct { + name string + funcTable localFuncTable + getRepoPassword func(velerocredentials.SecretStore, RepoParam) (string, error) + getStorageCredentials func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) + getStorageVariables func(*velerov1api.BackupStorageLocation, string) (map[string]string, error) + expected udmrepo.RepoOptions + expectedErr string + }{ + { + name: "get repo password fail", + funcTable: localFuncTable{ + getRepoPassword: func(velerocredentials.SecretStore, RepoParam) (string, error) { + return "", errors.New("fake-error-1") + }, + }, + expectedErr: "error to get repo password: fake-error-1", + }, + { + name: "get storage variable fail", + funcTable: localFuncTable{ + getRepoPassword: func(velerocredentials.SecretStore, RepoParam) (string, error) { + return "fake-password", nil + }, + getStorageVariables: func(*velerov1api.BackupStorageLocation, string) (map[string]string, error) { + return map[string]string{}, errors.New("fake-error-2") + }, + }, + expectedErr: "error to get storage variables: fake-error-2", + }, + { + name: "get storage credentials fail", + funcTable: localFuncTable{ + getRepoPassword: func(velerocredentials.SecretStore, RepoParam) (string, error) { + return "fake-password", nil + }, + getStorageVariables: func(*velerov1api.BackupStorageLocation, string) (map[string]string, error) { + return map[string]string{}, nil + }, + getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) { + return map[string]string{}, errors.New("fake-error-3") + }, + }, + expectedErr: "error to get repo credentials: fake-error-3", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + funcTable = tc.funcTable + urp := unifiedRepoProvider{} + + password, err := urp.getRepoOption(RepoParam{}) + + require.Equal(t, tc.expected, password) + + if tc.expectedErr == "" { + assert.NoError(t, err) + } else { + assert.EqualError(t, err, tc.expectedErr) + } + }) + } +} diff --git a/pkg/restic/repository_manager.go b/pkg/restic/repository_manager.go index f0ab633868..69927b851c 100644 --- a/pkg/restic/repository_manager.go +++ b/pkg/restic/repository_manager.go @@ -36,6 +36,7 @@ import ( velerov1client "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1" velerov1informers "github.com/vmware-tanzu/velero/pkg/generated/informers/externalversions/velero/v1" velerov1listers "github.com/vmware-tanzu/velero/pkg/generated/listers/velero/v1" + repokey "github.com/vmware-tanzu/velero/pkg/repository/keys" veleroexec "github.com/vmware-tanzu/velero/pkg/util/exec" "github.com/vmware-tanzu/velero/pkg/util/filesystem" ) @@ -242,7 +243,7 @@ func (rm *repositoryManager) Forget(ctx context.Context, snapshot SnapshotIdenti } func (rm *repositoryManager) exec(cmd *Command, backupLocation string) error { - file, err := rm.credentialsFileStore.Path(RepoKeySelector()) + file, err := rm.credentialsFileStore.Path(repokey.RepoKeySelector()) if err != nil { return err }