Use controller-runtime client to get restic secrets ()

* Use kubebuilder client for fetching restic secrets

Instead of using a SecretInformer for fetching secrets for restic, use
the cached client provided by the controller-runtime manager.

In order to use this client, the scheme for Secrets must be added to the
scheme used by the manager so this is added when creating the manager in
both the velero and restic servers.

This change also refactors some of the tests to add a shared utility for
creating a fake controller-runtime client which is now used among all
tests which use that client. This has been added to ensure that all
tests use the same client with the same scheme.

Signed-off-by: Bridget McErlean <bmcerlean@vmware.com>

* Add builder for SecretKeySelector

Signed-off-by: Bridget McErlean <bmcerlean@vmware.com>
pull/3477/head
Bridget McErlean 2021-02-18 13:30:52 -05:00 committed by GitHub
parent 6bdd4ac192
commit 9dbd238c89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 309 additions and 220 deletions

View File

@ -1,5 +1,5 @@
/*
Copyright 2019 the Velero contributors.
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.
@ -55,3 +55,9 @@ func (b *SecretBuilder) ObjectMeta(opts ...ObjectMetaOpt) *SecretBuilder {
return b
}
// Data sets the Secret data.
func (b *SecretBuilder) Data(data map[string][]byte) *SecretBuilder {
b.object.Data = data
return b
}

View File

@ -0,0 +1,43 @@
/*
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 builder
import (
corev1api "k8s.io/api/core/v1"
)
// SecretKeySelectorBuilder builds SecretKeySelector objects.
type SecretKeySelectorBuilder struct {
object *corev1api.SecretKeySelector
}
// ForSecretKeySelector is the constructor for a SecretKeySelectorBuilder.
func ForSecretKeySelector(name string, key string) *SecretKeySelectorBuilder {
return &SecretKeySelectorBuilder{
object: &corev1api.SecretKeySelector{
LocalObjectReference: corev1api.LocalObjectReference{
Name: name,
},
Key: key,
},
}
}
// Result returns the built SecretKeySelector.
func (b *SecretKeySelectorBuilder) Result() *corev1api.SecretKeySelector {
return b.object
}

View File

@ -27,12 +27,12 @@ import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
corev1api "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/builder"
"github.com/vmware-tanzu/velero/pkg/client"
"github.com/vmware-tanzu/velero/pkg/cmd"
"github.com/vmware-tanzu/velero/pkg/cmd/util/flag"
@ -178,13 +178,8 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
CACert: caCertData,
},
},
Config: o.Config.Data(),
Credential: &corev1api.SecretKeySelector{
LocalObjectReference: corev1api.LocalObjectReference{
Name: secretName,
},
Key: secretKey,
},
Config: o.Config.Data(),
Credential: builder.ForSecretKeySelector(secretName, secretKey).Result(),
Default: o.DefaultBackupStorageLocation,
AccessMode: velerov1api.BackupStorageLocationAccessMode(o.AccessMode.String()),
BackupSyncPeriod: backupSyncPeriod,

View File

@ -25,11 +25,11 @@ import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
corev1api "k8s.io/api/core/v1"
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/builder"
"github.com/vmware-tanzu/velero/pkg/client"
"github.com/vmware-tanzu/velero/pkg/cmd"
"github.com/vmware-tanzu/velero/pkg/cmd/util/flag"
@ -139,13 +139,8 @@ func (o *SetOptions) Run(c *cobra.Command, f client.Factory) error {
location.Spec.Default = o.DefaultBackupStorageLocation
location.Spec.StorageType.ObjectStorage.CACert = caCertData
for k, v := range o.Credential.Data() {
location.Spec.Credential = &corev1api.SecretKeySelector{
LocalObjectReference: corev1api.LocalObjectReference{
Name: k,
},
Key: v,
}
for name, key := range o.Credential.Data() {
location.Spec.Credential = builder.ForSecretKeySelector(name, key).Result()
break
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2020 the Velero contributors.
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.
@ -49,7 +49,6 @@ import (
"github.com/vmware-tanzu/velero/pkg/controller"
clientset "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned"
informers "github.com/vmware-tanzu/velero/pkg/generated/informers/externalversions"
"github.com/vmware-tanzu/velero/pkg/restic"
"github.com/vmware-tanzu/velero/pkg/util/filesystem"
"github.com/vmware-tanzu/velero/pkg/util/logging"
@ -102,7 +101,6 @@ type resticServer struct {
veleroInformerFactory informers.SharedInformerFactory
kubeInformerFactory kubeinformers.SharedInformerFactory
podInformer cache.SharedIndexInformer
secretInformer cache.SharedIndexInformer
logger logrus.FieldLogger
ctx context.Context
cancelFunc context.CancelFunc
@ -136,22 +134,6 @@ func newResticServer(logger logrus.FieldLogger, factory client.Factory, metricAd
},
)
// use a stand-alone secrets informer so we can filter to only the restic credentials
// secret(s) within the velero namespace
//
// note: using an informer to access the single secret for all velero-managed
// restic repositories is overkill for now, but will be useful when we move
// to fully-encrypted backups and have unique keys per repository.
secretInformer := corev1informers.NewFilteredSecretInformer(
kubeClient,
factory.Namespace(),
0,
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
func(opts *metav1.ListOptions) {
opts.FieldSelector = fmt.Sprintf("metadata.name=%s", restic.CredentialsSecretName)
},
)
ctx, cancelFunc := context.WithCancel(context.Background())
clientConfig, err := factory.ClientConfig()
@ -160,7 +142,9 @@ func newResticServer(logger logrus.FieldLogger, factory client.Factory, metricAd
}
ctrl.SetLogger(zap.New(zap.UseDevMode(true)))
velerov1api.AddToScheme(scheme)
v1.AddToScheme(scheme)
mgr, err := ctrl.NewManager(clientConfig, ctrl.Options{
Scheme: scheme,
})
@ -174,7 +158,6 @@ func newResticServer(logger logrus.FieldLogger, factory client.Factory, metricAd
veleroInformerFactory: informers.NewFilteredSharedInformerFactory(veleroClient, 0, factory.Namespace(), nil),
kubeInformerFactory: kubeinformers.NewSharedInformerFactory(kubeClient, 0),
podInformer: podInformer,
secretInformer: secretInformer,
logger: logger,
ctx: ctx,
cancelFunc: cancelFunc,
@ -212,7 +195,6 @@ func (s *resticServer) run() {
s.veleroInformerFactory.Velero().V1().PodVolumeBackups(),
s.veleroClient.VeleroV1(),
s.podInformer,
s.secretInformer,
s.kubeInformerFactory.Core().V1().PersistentVolumeClaims(),
s.kubeInformerFactory.Core().V1().PersistentVolumes(),
s.metrics,
@ -225,7 +207,6 @@ func (s *resticServer) run() {
s.veleroInformerFactory.Velero().V1().PodVolumeRestores(),
s.veleroClient.VeleroV1(),
s.podInformer,
s.secretInformer,
s.kubeInformerFactory.Core().V1().PersistentVolumeClaims(),
s.kubeInformerFactory.Core().V1().PersistentVolumes(),
s.mgr.GetClient(),
@ -235,7 +216,6 @@ func (s *resticServer) run() {
go s.veleroInformerFactory.Start(s.ctx.Done())
go s.kubeInformerFactory.Start(s.ctx.Done())
go s.podInformer.Run(s.ctx.Done())
go s.secretInformer.Run(s.ctx.Done())
// TODO(2.0): presuming all controllers and resources are converted to runtime-controller
// by v2.0, the block from this line and including the `s.mgr.Start() will be

View File

@ -24,10 +24,10 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"
corev1api "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/builder"
"github.com/vmware-tanzu/velero/pkg/client"
"github.com/vmware-tanzu/velero/pkg/cmd"
"github.com/vmware-tanzu/velero/pkg/cmd/util/flag"
@ -113,14 +113,9 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
Labels: o.Labels.Data(),
},
Spec: api.VolumeSnapshotLocationSpec{
Provider: o.Provider,
Config: o.Config.Data(),
Credential: &corev1api.SecretKeySelector{
LocalObjectReference: corev1api.LocalObjectReference{
Name: o.secretName,
},
Key: o.secretKey,
},
Provider: o.Provider,
Config: o.Config.Data(),
Credential: builder.ForSecretKeySelector(o.secretName, o.secretKey).Result(),
},
}

View File

@ -23,11 +23,11 @@ import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
corev1api "k8s.io/api/core/v1"
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/builder"
"github.com/vmware-tanzu/velero/pkg/client"
"github.com/vmware-tanzu/velero/pkg/cmd"
"github.com/vmware-tanzu/velero/pkg/cmd/util/flag"
@ -99,13 +99,8 @@ func (o *SetOptions) Run(c *cobra.Command, f client.Factory) error {
return errors.WithStack(err)
}
for k, v := range o.Credential.Data() {
location.Spec.Credential = &corev1api.SecretKeySelector{
LocalObjectReference: corev1api.LocalObjectReference{
Name: k,
},
Key: v,
}
for name, key := range o.Credential.Data() {
location.Spec.Credential = builder.ForSecretKeySelector(name, key).Result()
break
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2020 the Velero contributors.
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.
@ -31,6 +31,7 @@ import (
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
corev1api "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
@ -40,10 +41,8 @@ import (
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
corev1informers "k8s.io/client-go/informers/core/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
snapshotv1beta1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1beta1"
snapshotv1beta1client "github.com/kubernetes-csi/external-snapshotter/client/v4/clientset/versioned"
@ -286,6 +285,8 @@ func newServer(f client.Factory, config serverConfig, logger *logrus.Logger) (*s
scheme := runtime.NewScheme()
velerov1api.AddToScheme(scheme)
corev1api.AddToScheme(scheme)
mgr, err := ctrl.NewManager(clientConfig, ctrl.Options{
Scheme: scheme,
})
@ -483,28 +484,10 @@ func (s *server) initRestic() error {
return err
}
// use a stand-alone secrets informer so we can filter to only the restic credentials
// secret(s) within the velero namespace
//
// note: using an informer to access the single secret for all velero-managed
// restic repositories is overkill for now, but will be useful when we move
// to fully-encrypted backups and have unique keys per repository.
secretsInformer := corev1informers.NewFilteredSecretInformer(
s.kubeClient,
s.namespace,
0,
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
func(opts *metav1.ListOptions) {
opts.FieldSelector = fmt.Sprintf("metadata.name=%s", restic.CredentialsSecretName)
},
)
go secretsInformer.Run(s.ctx.Done())
res, err := restic.NewRepositoryManager(
s.ctx,
s.namespace,
s.veleroClient,
secretsInformer,
s.sharedInformerFactory.Velero().V1().ResticRepositories(),
s.veleroClient.VeleroV1(),
s.mgr.GetClient(),

View File

@ -1,5 +1,5 @@
/*
Copyright 2020 the Velero contributors.
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.
@ -181,9 +181,9 @@ func TestProcessBackupValidationFailures(t *testing.T) {
var fakeClient kbclient.Client
if test.backupLocation != nil {
fakeClient = newFakeClient(t, test.backupLocation)
fakeClient = velerotest.NewFakeControllerRuntimeClient(t, test.backupLocation)
} else {
fakeClient = newFakeClient(t)
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
}
c := &backupController{
@ -246,7 +246,7 @@ func TestBackupLocationLabel(t *testing.T) {
clientset = fake.NewSimpleClientset(test.backup)
sharedInformers = informers.NewSharedInformerFactory(clientset, 0)
logger = logging.DefaultLogger(logrus.DebugLevel, formatFlag)
fakeClient = newFakeClient(t)
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
)
apiServer := velerotest.NewAPIServer(t)
@ -306,7 +306,7 @@ func TestDefaultBackupTTL(t *testing.T) {
formatFlag := logging.FormatText
var (
clientset = fake.NewSimpleClientset(test.backup)
fakeClient = newFakeClient(t)
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
logger = logging.DefaultLogger(logrus.DebugLevel, formatFlag)
sharedInformers = informers.NewSharedInformerFactory(clientset, 0)
)
@ -783,9 +783,9 @@ func TestProcessBackupCompletions(t *testing.T) {
var fakeClient kbclient.Client
// add the test's backup storage location if it's different than the default
if test.backupLocation != nil && test.backupLocation != defaultBackupLocation {
fakeClient = newFakeClient(t, test.backupLocation)
fakeClient = velerotest.NewFakeControllerRuntimeClient(t, test.backupLocation)
} else {
fakeClient = newFakeClient(t)
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
}
apiServer := velerotest.NewAPIServer(t)

View File

@ -1,5 +1,5 @@
/*
Copyright 2020 the Velero contributors.
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.
@ -144,7 +144,7 @@ func setupBackupDeletionControllerTest(t *testing.T, objects ...runtime.Object)
var (
client = fake.NewSimpleClientset(append(objects, req)...)
fakeClient = newFakeClient(t, objects...)
fakeClient = velerotest.NewFakeControllerRuntimeClient(t, objects...)
sharedInformers = informers.NewSharedInformerFactory(client, 0)
volumeSnapshotter = &velerotest.FakeVolumeSnapshotter{SnapshotsTaken: sets.NewString()}
pluginManager = &pluginmocks.Manager{}
@ -1112,7 +1112,7 @@ func TestBackupDeletionControllerDeleteExpiredRequests(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
client := fake.NewSimpleClientset()
fakeClient := newFakeClient(t)
fakeClient := velerotest.NewFakeControllerRuntimeClient(t)
sharedInformers := informers.NewSharedInformerFactory(client, 0)
controller := NewBackupDeletionController(

View File

@ -1,5 +1,5 @@
/*
Copyright 2017, 2020 the Velero contributors.
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.
@ -335,7 +335,7 @@ func TestBackupSyncControllerRun(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
var (
client = fake.NewSimpleClientset()
fakeClient = newFakeClient(t)
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
sharedInformers = informers.NewSharedInformerFactory(client, 0)
pluginManager = &pluginmocks.Manager{}
backupStores = make(map[string]*persistencemocks.BackupStore)
@ -558,7 +558,7 @@ func TestDeleteOrphanedBackups(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
var (
client = fake.NewSimpleClientset()
fakeClient = newFakeClient(t)
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
sharedInformers = informers.NewSharedInformerFactory(client, 0)
)
@ -652,7 +652,7 @@ func TestStorageLabelsInDeleteOrphanedBackups(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
var (
client = fake.NewSimpleClientset()
fakeClient = newFakeClient(t)
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
sharedInformers = informers.NewSharedInformerFactory(client, 0)
)

View File

@ -1,5 +1,5 @@
/*
Copyright 2017 the Velero contributors.
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.
@ -246,9 +246,9 @@ func TestProcessDownloadRequest(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
var fakeClient client.Client
if tc.backupLocation != nil {
fakeClient = newFakeClient(t, tc.backupLocation)
fakeClient = velerotest.NewFakeControllerRuntimeClient(t, tc.backupLocation)
} else {
fakeClient = newFakeClient(t)
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
}
harness := newDownloadRequestTestHarness(t, fakeClient)

View File

@ -1,5 +1,5 @@
/*
Copyright 2017, 2019 the Velero contributors.
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.
@ -251,9 +251,9 @@ func TestGCControllerProcessQueueItem(t *testing.T) {
var fakeClient kbclient.Client
if test.backupLocation != nil {
fakeClient = newFakeClient(t, test.backupLocation)
fakeClient = velerotest.NewFakeControllerRuntimeClient(t, test.backupLocation)
} else {
fakeClient = newFakeClient(t)
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
}
controller := NewGCController(

View File

@ -1,5 +1,5 @@
/*
Copyright 2018, 2019 the Velero contributors.
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.
@ -54,7 +54,6 @@ type podVolumeBackupController struct {
podVolumeBackupClient velerov1client.PodVolumeBackupsGetter
podVolumeBackupLister listers.PodVolumeBackupLister
secretLister corev1listers.SecretLister
podLister corev1listers.PodLister
pvcLister corev1listers.PersistentVolumeClaimLister
pvLister corev1listers.PersistentVolumeLister
@ -73,7 +72,6 @@ func NewPodVolumeBackupController(
podVolumeBackupInformer informers.PodVolumeBackupInformer,
podVolumeBackupClient velerov1client.PodVolumeBackupsGetter,
podInformer cache.SharedIndexInformer,
secretInformer cache.SharedIndexInformer,
pvcInformer corev1informers.PersistentVolumeClaimInformer,
pvInformer corev1informers.PersistentVolumeInformer,
metrics *metrics.ServerMetrics,
@ -85,7 +83,6 @@ func NewPodVolumeBackupController(
podVolumeBackupClient: podVolumeBackupClient,
podVolumeBackupLister: podVolumeBackupInformer.Lister(),
podLister: corev1listers.NewPodLister(podInformer.GetIndexer()),
secretLister: corev1listers.NewSecretLister(secretInformer.GetIndexer()),
pvcLister: pvcInformer.Lister(),
pvLister: pvInformer.Lister(),
kbClient: kbClient,
@ -101,7 +98,6 @@ func NewPodVolumeBackupController(
c.cacheSyncWaiters,
podVolumeBackupInformer.Informer().HasSynced,
podInformer.HasSynced,
secretInformer.HasSynced,
pvcInformer.Informer().HasSynced,
)
c.processBackupFunc = c.processBackup
@ -225,7 +221,7 @@ func (c *podVolumeBackupController) processBackup(req *velerov1api.PodVolumeBack
log.WithField("path", path).Debugf("Found path matching glob")
// temp creds
credentialsFile, err := restic.TempCredentialsFile(c.secretLister, req.Namespace, req.Spec.Pod.Namespace, c.fileSystem)
credentialsFile, err := restic.TempCredentialsFile(c.kbClient, req.Namespace, req.Spec.Pod.Namespace, c.fileSystem)
if err != nil {
log.WithError(err).Error("Error creating temp restic credentials file")
return c.fail(req, errors.Wrap(err, "error creating temp restic credentials file").Error(), log)

View File

@ -1,5 +1,5 @@
/*
Copyright 2020 the Velero contributors.
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.
@ -56,7 +56,6 @@ type podVolumeRestoreController struct {
podVolumeRestoreClient velerov1client.PodVolumeRestoresGetter
podVolumeRestoreLister listers.PodVolumeRestoreLister
podLister corev1listers.PodLister
secretLister corev1listers.SecretLister
pvcLister corev1listers.PersistentVolumeClaimLister
pvLister corev1listers.PersistentVolumeLister
backupLocationInformer k8scache.Informer
@ -74,7 +73,6 @@ func NewPodVolumeRestoreController(
podVolumeRestoreInformer informers.PodVolumeRestoreInformer,
podVolumeRestoreClient velerov1client.PodVolumeRestoresGetter,
podInformer cache.SharedIndexInformer,
secretInformer cache.SharedIndexInformer,
pvcInformer corev1informers.PersistentVolumeClaimInformer,
pvInformer corev1informers.PersistentVolumeInformer,
kbClient client.Client,
@ -85,7 +83,6 @@ func NewPodVolumeRestoreController(
podVolumeRestoreClient: podVolumeRestoreClient,
podVolumeRestoreLister: podVolumeRestoreInformer.Lister(),
podLister: corev1listers.NewPodLister(podInformer.GetIndexer()),
secretLister: corev1listers.NewSecretLister(secretInformer.GetIndexer()),
pvcLister: pvcInformer.Lister(),
pvLister: pvInformer.Lister(),
kbClient: kbClient,
@ -100,7 +97,6 @@ func NewPodVolumeRestoreController(
c.cacheSyncWaiters,
podVolumeRestoreInformer.Informer().HasSynced,
podInformer.HasSynced,
secretInformer.HasSynced,
pvcInformer.Informer().HasSynced,
)
c.processRestoreFunc = c.processRestore
@ -304,7 +300,7 @@ func (c *podVolumeRestoreController) processRestore(req *velerov1api.PodVolumeRe
return c.failRestore(req, errors.Wrap(err, "error getting volume directory name").Error(), log)
}
credsFile, err := restic.TempCredentialsFile(c.secretLister, req.Namespace, req.Spec.Pod.Namespace, c.fileSystem)
credsFile, err := restic.TempCredentialsFile(c.kbClient, req.Namespace, req.Spec.Pod.Namespace, c.fileSystem)
if err != nil {
log.WithError(err).Error("Error creating temp restic credentials file")
return c.failRestore(req, errors.Wrap(err, "error creating temp restic credentials file").Error(), log)

View File

@ -1,5 +1,5 @@
/*
Copyright 2020 the Velero contributors.
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.
@ -94,7 +94,7 @@ func TestFetchBackupInfo(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
var (
client = fake.NewSimpleClientset()
fakeClient = newFakeClient(t)
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
restorer = &fakeRestorer{}
sharedInformers = informers.NewSharedInformerFactory(client, 0)
logger = velerotest.NewLogger()
@ -409,7 +409,7 @@ func TestProcessQueueItem(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
var (
client = fake.NewSimpleClientset()
fakeClient = newFakeClient(t)
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
restorer = &fakeRestorer{}
sharedInformers = informers.NewSharedInformerFactory(client, 0)
logger = velerotest.NewLogger()

View File

@ -1,5 +1,5 @@
/*
Copyright 2020 the Velero contributors.
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.
@ -27,17 +27,13 @@ import (
"github.com/vmware-tanzu/velero/pkg/persistence"
persistencemocks "github.com/vmware-tanzu/velero/pkg/persistence/mocks"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/klog"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/envtest"
"sigs.k8s.io/controller-runtime/pkg/manager"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/stretchr/testify/require"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"k8s.io/client-go/kubernetes/scheme"
@ -98,6 +94,7 @@ type testEnvironment struct {
// This function should be called only once for each package you're running tests within,
// usually the environment is initialized in a suite_test.go file within a `BeforeSuite` ginkgo block.
func newTestEnvironment() *testEnvironment {
// scheme.Scheme is initialized with all native Kubernetes types
err := velerov1api.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
@ -133,12 +130,6 @@ func (t *testEnvironment) stop() error {
return env.Stop()
}
func newFakeClient(t *testing.T, initObjs ...runtime.Object) client.Client {
err := velerov1api.AddToScheme(scheme.Scheme)
require.NoError(t, err)
return fake.NewFakeClientWithScheme(scheme.Scheme, initObjs...)
}
type fakeSingleObjectBackupStoreGetter struct {
store persistence.BackupStore
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2018 the Velero contributors.
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.
@ -29,12 +29,13 @@ import (
corev1api "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
corev1listers "k8s.io/client-go/listers/core/v1"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/builder"
velerov1listers "github.com/vmware-tanzu/velero/pkg/generated/listers/velero/v1"
"github.com/vmware-tanzu/velero/pkg/label"
"github.com/vmware-tanzu/velero/pkg/util/filesystem"
"github.com/vmware-tanzu/velero/pkg/util/kube"
)
const (
@ -241,14 +242,14 @@ func GetSnapshotsInBackup(backup *velerov1api.Backup, podVolumeBackupLister vele
// encryption key for the given repo and returns its path. The
// caller should generally call os.Remove() to remove the file
// when done with it.
func TempCredentialsFile(secretLister corev1listers.SecretLister, veleroNamespace, repoName string, fs filesystem.Interface) (string, error) {
secretGetter := NewListerSecretGetter(secretLister)
func TempCredentialsFile(client kbclient.Client, veleroNamespace, repoName string, fs filesystem.Interface) (string, error) {
// 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 GetRepositoryKey will need to take the repo
// name as an argument as well.
repoKey, err := GetRepositoryKey(secretGetter, veleroNamespace)
// (all within the Velero server's namespace) so repoKeySelector will need to select the key
// for that repo.
repoKeySelector := builder.ForSecretKeySelector(CredentialsSecretName, CredentialsKey).Result()
repoKey, err := kube.GetSecretKey(client, veleroNamespace, repoKeySelector)
if err != nil {
return "", err
}
@ -261,7 +262,7 @@ func TempCredentialsFile(secretLister corev1listers.SecretLister, veleroNamespac
if _, err := file.Write(repoKey); err != nil {
// nothing we can do about an error closing the file here, and we're
// already returning an error about the write failing.
file.Close()
_ = file.Close()
return "", errors.WithStack(err)
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2018 the Velero contributors.
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.
@ -22,17 +22,10 @@ import (
"sort"
"testing"
"k8s.io/apimachinery/pkg/runtime"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1api "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/scheme"
corev1listers "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
k8sfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/builder"
@ -352,10 +345,9 @@ func TestGetSnapshotsInBackup(t *testing.T) {
func TestTempCredentialsFile(t *testing.T) {
var (
secretInformer = cache.NewSharedIndexInformer(nil, new(corev1api.Secret), 0, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
secretLister = corev1listers.NewSecretLister(secretInformer.GetIndexer())
fs = velerotest.NewFakeFileSystem()
secret = &corev1api.Secret{
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
fs = velerotest.NewFakeFileSystem()
secret = &corev1api.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: "velero",
Name: CredentialsSecretName,
@ -366,15 +358,15 @@ func TestTempCredentialsFile(t *testing.T) {
}
)
// secret not in lister: expect an error
fileName, err := TempCredentialsFile(secretLister, "velero", "default", fs)
// secret not in server: expect an error
fileName, err := TempCredentialsFile(fakeClient, "velero", "default", fs)
assert.Error(t, err)
// now add secret to lister
require.NoError(t, secretInformer.GetStore().Add(secret))
// now add secret
require.NoError(t, fakeClient.Create(context.Background(), secret))
// secret in lister: expect temp file to be created with password
fileName, err = TempCredentialsFile(secretLister, "velero", "default", fs)
// secret in server: expect temp file to be created with password
fileName, err = TempCredentialsFile(fakeClient, "velero", "default", fs)
require.NoError(t, err)
contents, err := fs.ReadFile(fileName)
@ -400,7 +392,7 @@ func TestTempCACertFile(t *testing.T) {
}
)
fakeClient := newFakeClient(t)
fakeClient := velerotest.NewFakeControllerRuntimeClient(t)
fakeClient.Create(context.Background(), bsl)
// expect temp file to be created with cacert value
@ -645,9 +637,3 @@ func TestIsPVBMatchPod(t *testing.T) {
}
}
func newFakeClient(t *testing.T, initObjs ...runtime.Object) client.Client {
err := velerov1api.AddToScheme(scheme.Scheme)
require.NoError(t, err)
return k8sfake.NewFakeClientWithScheme(scheme.Scheme, initObjs...)
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2018 the Velero contributors.
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.
@ -24,7 +24,6 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
corev1listers "k8s.io/client-go/listers/core/v1"
)
const (
@ -62,53 +61,3 @@ func EnsureCommonRepositoryKey(secretClient corev1client.SecretsGetter, namespac
return nil
}
type SecretGetter interface {
GetSecret(namespace, name string) (*corev1api.Secret, error)
}
type clientSecretGetter struct {
client corev1client.SecretsGetter
}
func NewClientSecretGetter(client corev1client.SecretsGetter) SecretGetter {
return &clientSecretGetter{client: client}
}
func (c *clientSecretGetter) GetSecret(namespace, name string) (*corev1api.Secret, error) {
secret, err := c.client.Secrets(namespace).Get(context.TODO(), name, metav1.GetOptions{})
if err != nil {
return nil, errors.WithStack(err)
}
return secret, nil
}
type listerSecretGetter struct {
lister corev1listers.SecretLister
}
func NewListerSecretGetter(lister corev1listers.SecretLister) SecretGetter {
return &listerSecretGetter{lister: lister}
}
func (l *listerSecretGetter) GetSecret(namespace, name string) (*corev1api.Secret, error) {
secret, err := l.lister.Secrets(namespace).Get(name)
if err != nil {
return nil, errors.WithStack(err)
}
return secret, nil
}
func GetRepositoryKey(secretGetter SecretGetter, namespace string) ([]byte, error) {
secret, err := secretGetter.GetSecret(namespace, CredentialsSecretName)
if err != nil {
return nil, err
}
key, found := secret.Data[CredentialsKey]
if !found {
return nil, errors.Errorf("%q secret is missing data for key %q", CredentialsSecretName, CredentialsKey)
}
return key, nil
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2018, 2019 the Velero contributors.
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.
@ -26,7 +26,6 @@ import (
"github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
corev1listers "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache"
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
@ -83,7 +82,6 @@ type RestorerFactory interface {
type repositoryManager struct {
namespace string
veleroClient clientset.Interface
secretsLister corev1listers.SecretLister
repoLister velerov1listers.ResticRepositoryLister
repoInformerSynced cache.InformerSynced
kbClient kbclient.Client
@ -101,7 +99,6 @@ func NewRepositoryManager(
ctx context.Context,
namespace string,
veleroClient clientset.Interface,
secretsInformer cache.SharedIndexInformer,
repoInformer velerov1informers.ResticRepositoryInformer,
repoClient velerov1client.ResticRepositoriesGetter,
kbClient kbclient.Client,
@ -112,7 +109,6 @@ func NewRepositoryManager(
rm := &repositoryManager{
namespace: namespace,
veleroClient: veleroClient,
secretsLister: corev1listers.NewSecretLister(secretsInformer.GetIndexer()),
repoLister: repoInformer.Lister(),
repoInformerSynced: repoInformer.Informer().HasSynced,
kbClient: kbClient,
@ -126,10 +122,6 @@ func NewRepositoryManager(
fileSystem: filesystem.NewFileSystem(),
}
if !cache.WaitForCacheSync(ctx.Done(), secretsInformer.HasSynced) {
return nil, errors.New("timed out waiting for cache to sync")
}
return rm, nil
}
@ -235,7 +227,7 @@ func (rm *repositoryManager) Forget(ctx context.Context, snapshot SnapshotIdenti
}
func (rm *repositoryManager) exec(cmd *Command, backupLocation string) error {
file, err := TempCredentialsFile(rm.secretsLister, rm.namespace, cmd.RepoName(), rm.fileSystem)
file, err := TempCredentialsFile(rm.kbClient, rm.namespace, cmd.RepoName(), rm.fileSystem)
if err != nil {
return err
}

View File

@ -0,0 +1,38 @@
/*
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 test
import (
"testing"
"github.com/stretchr/testify/require"
corev1api "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
k8sfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
)
func NewFakeControllerRuntimeClient(t *testing.T, initObjs ...runtime.Object) client.Client {
scheme := runtime.NewScheme()
err := velerov1api.AddToScheme(scheme)
require.NoError(t, err)
err = corev1api.AddToScheme(scheme)
require.NoError(t, err)
return k8sfake.NewFakeClientWithScheme(scheme, initObjs...)
}

51
pkg/util/kube/secrets.go Normal file
View File

@ -0,0 +1,51 @@
/*
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 kube
import (
"context"
"github.com/pkg/errors"
corev1api "k8s.io/api/core/v1"
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
)
func GetSecret(client kbclient.Client, namespace, name string) (*corev1api.Secret, error) {
secret := &corev1api.Secret{}
if err := client.Get(context.TODO(), kbclient.ObjectKey{
Namespace: namespace,
Name: name,
}, secret); err != nil {
return nil, err
}
return secret, nil
}
func GetSecretKey(client kbclient.Client, namespace string, selector *corev1api.SecretKeySelector) ([]byte, error) {
secret, err := GetSecret(client, namespace, selector.Name)
if err != nil {
return nil, err
}
key, found := secret.Data[selector.Key]
if !found {
return nil, errors.Errorf("%q secret is missing data for key %q", selector.Name, selector.Key)
}
return key, nil
}

View File

@ -0,0 +1,97 @@
/*
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 kube
import (
"context"
"testing"
"github.com/stretchr/testify/require"
corev1api "k8s.io/api/core/v1"
"github.com/vmware-tanzu/velero/pkg/builder"
velerotest "github.com/vmware-tanzu/velero/pkg/test"
)
func TestGetSecretKey(t *testing.T) {
testCases := []struct {
name string
secrets []*corev1api.Secret
namespace string
selector *corev1api.SecretKeySelector
expectedData string
expectedErr string
}{
{
name: "error is returned when secret doesn't exist",
secrets: []*corev1api.Secret{
builder.ForSecret("ns-1", "secret1").Data(map[string][]byte{
"key1": []byte("ns1-secretdata1"),
}).Result(),
},
namespace: "ns-1",
selector: builder.ForSecretKeySelector("non-existent-secret", "key2").Result(),
expectedErr: "secrets \"non-existent-secret\" not found",
},
{
name: "error is returned when key is missing from secret",
secrets: []*corev1api.Secret{
builder.ForSecret("ns-1", "secret1").Data(map[string][]byte{
"key1": []byte("ns1-secretdata1"),
}).Result(),
},
namespace: "ns-1",
selector: builder.ForSecretKeySelector("secret1", "non-existent-key").Result(),
expectedErr: "\"secret1\" secret is missing data for key \"non-existent-key\"",
},
{
name: "key specified in selector is returned",
secrets: []*corev1api.Secret{
builder.ForSecret("ns-1", "secret1").Data(map[string][]byte{
"key1": []byte("ns1-secretdata1"),
"key2": []byte("ns1-secretdata2"),
}).Result(),
builder.ForSecret("ns-2", "secret1").Data(map[string][]byte{
"key1": []byte("ns2-secretdata1"),
"key2": []byte("ns2-secretdata2"),
}).Result(),
},
namespace: "ns-2",
selector: builder.ForSecretKeySelector("secret1", "key2").Result(),
expectedData: "ns2-secretdata2",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
fakeClient := velerotest.NewFakeControllerRuntimeClient(t)
for _, secret := range tc.secrets {
require.NoError(t, fakeClient.Create(context.Background(), secret))
}
data, err := GetSecretKey(fakeClient, tc.namespace, tc.selector)
if tc.expectedErr != "" {
require.Nil(t, data)
require.EqualError(t, err, tc.expectedErr)
} else {
require.NoError(t, err)
require.Equal(t, tc.expectedData, string(data))
}
})
}
}