Refactor the restic repo related code for Kopia integration
Refactor the restic repo related code for Kopia integration Signed-off-by: Wenkai Yin(尹文开) <yinw@vmware.com>pull/5190/head
parent
4e25f59dc1
commit
047c7531fa
|
@ -51,6 +51,9 @@ const (
|
|||
BackupRepositoryPhaseNew BackupRepositoryPhase = "New"
|
||||
BackupRepositoryPhaseReady BackupRepositoryPhase = "Ready"
|
||||
BackupRepositoryPhaseNotReady BackupRepositoryPhase = "NotReady"
|
||||
|
||||
BackupRepositoryTypeRestic string = "restic"
|
||||
BackupRepositoryTypeUnified string = "unified"
|
||||
)
|
||||
|
||||
// BackupRepositoryStatus is the current status of a BackupRepository.
|
||||
|
|
|
@ -82,6 +82,8 @@ 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"
|
||||
"github.com/vmware-tanzu/velero/pkg/podvolume"
|
||||
"github.com/vmware-tanzu/velero/pkg/repository"
|
||||
repokey "github.com/vmware-tanzu/velero/pkg/repository/keys"
|
||||
)
|
||||
|
||||
|
@ -248,7 +250,9 @@ type server struct {
|
|||
logger logrus.FieldLogger
|
||||
logLevel logrus.Level
|
||||
pluginRegistry clientmgmt.Registry
|
||||
resticManager restic.RepositoryManager
|
||||
repoManager repository.Manager
|
||||
repoLocker *repository.RepoLocker
|
||||
repoEnsurer *repository.RepositoryEnsurer
|
||||
metrics *metrics.ServerMetrics
|
||||
config serverConfig
|
||||
mgr manager.Manager
|
||||
|
@ -536,22 +540,10 @@ func (s *server) initRestic() error {
|
|||
return err
|
||||
}
|
||||
|
||||
res, err := restic.NewRepositoryManager(
|
||||
s.ctx,
|
||||
s.namespace,
|
||||
s.veleroClient,
|
||||
s.sharedInformerFactory.Velero().V1().BackupRepositories(),
|
||||
s.veleroClient.VeleroV1(),
|
||||
s.mgr.GetClient(),
|
||||
s.kubeClient.CoreV1(),
|
||||
s.kubeClient.CoreV1(),
|
||||
s.credentialFileStore,
|
||||
s.logger,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.resticManager = res
|
||||
s.repoLocker = repository.NewRepoLocker()
|
||||
s.repoEnsurer = repository.NewRepositoryEnsurer(s.sharedInformerFactory.Velero().V1().BackupRepositories(), s.veleroClient.VeleroV1(), s.logger)
|
||||
|
||||
s.repoManager = repository.NewManager(s.namespace, s.mgr.GetClient(), s.repoLocker, s.repoEnsurer, s.credentialFileStore, s.logger)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -643,7 +635,8 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
|
|||
s.discoveryHelper,
|
||||
client.NewDynamicFactory(s.dynamicClient),
|
||||
podexec.NewPodCommandExecutor(s.kubeClientConfig, s.kubeClient.CoreV1().RESTClient()),
|
||||
s.resticManager,
|
||||
podvolume.NewBackupperFactory(s.repoLocker, s.repoEnsurer, s.veleroClient, s.kubeClient.CoreV1(),
|
||||
s.kubeClient.CoreV1(), s.sharedInformerFactory.Velero().V1().BackupRepositories().Informer().HasSynced, s.logger),
|
||||
s.config.podVolumeOperationTimeout,
|
||||
s.config.defaultVolumesToRestic,
|
||||
s.config.clientPageSize,
|
||||
|
@ -704,7 +697,8 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
|
|||
client.NewDynamicFactory(s.dynamicClient),
|
||||
s.config.restoreResourcePriorities,
|
||||
s.kubeClient.CoreV1().Namespaces(),
|
||||
s.resticManager,
|
||||
podvolume.NewRestorerFactory(s.repoLocker, s.repoEnsurer, s.veleroClient, s.kubeClient.CoreV1(),
|
||||
s.sharedInformerFactory.Velero().V1().BackupRepositories().Informer().HasSynced, s.logger),
|
||||
s.config.podVolumeOperationTimeout,
|
||||
s.config.resourceTerminatingTimeout,
|
||||
s.logger,
|
||||
|
@ -812,7 +806,7 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
|
|||
s.logger.Fatal(err, "unable to create controller", "controller", controller.Schedule)
|
||||
}
|
||||
|
||||
if err := controller.NewResticRepoReconciler(s.namespace, s.logger, s.mgr.GetClient(), s.config.defaultResticMaintenanceFrequency, s.resticManager).SetupWithManager(s.mgr); err != nil {
|
||||
if err := controller.NewResticRepoReconciler(s.namespace, s.logger, s.mgr.GetClient(), s.config.defaultResticMaintenanceFrequency, s.repoManager).SetupWithManager(s.mgr); err != nil {
|
||||
s.logger.Fatal(err, "unable to create controller", "controller", controller.ResticRepo)
|
||||
}
|
||||
|
||||
|
@ -820,7 +814,7 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
|
|||
s.logger,
|
||||
s.mgr.GetClient(),
|
||||
backupTracker,
|
||||
s.resticManager,
|
||||
s.repoManager,
|
||||
s.metrics,
|
||||
s.discoveryHelper,
|
||||
newPluginManager,
|
||||
|
|
|
@ -40,6 +40,7 @@ import (
|
|||
"github.com/vmware-tanzu/velero/pkg/persistence"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
||||
"github.com/vmware-tanzu/velero/pkg/repository"
|
||||
"github.com/vmware-tanzu/velero/pkg/restic"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/filesystem"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/kube"
|
||||
|
@ -56,7 +57,7 @@ type backupDeletionReconciler struct {
|
|||
client.Client
|
||||
logger logrus.FieldLogger
|
||||
backupTracker BackupTracker
|
||||
resticMgr restic.RepositoryManager
|
||||
repoMgr repository.Manager
|
||||
metrics *metrics.ServerMetrics
|
||||
clock clock.Clock
|
||||
discoveryHelper discovery.Helper
|
||||
|
@ -69,7 +70,7 @@ func NewBackupDeletionReconciler(
|
|||
logger logrus.FieldLogger,
|
||||
client client.Client,
|
||||
backupTracker BackupTracker,
|
||||
resticMgr restic.RepositoryManager,
|
||||
repoMgr repository.Manager,
|
||||
metrics *metrics.ServerMetrics,
|
||||
helper discovery.Helper,
|
||||
newPluginManager func(logrus.FieldLogger) clientmgmt.Manager,
|
||||
|
@ -79,7 +80,7 @@ func NewBackupDeletionReconciler(
|
|||
Client: client,
|
||||
logger: logger,
|
||||
backupTracker: backupTracker,
|
||||
resticMgr: resticMgr,
|
||||
repoMgr: repoMgr,
|
||||
metrics: metrics,
|
||||
clock: clock.RealClock{},
|
||||
discoveryHelper: helper,
|
||||
|
@ -435,7 +436,7 @@ func (r *backupDeletionReconciler) deleteExistingDeletionRequests(ctx context.Co
|
|||
}
|
||||
|
||||
func (r *backupDeletionReconciler) deleteResticSnapshots(ctx context.Context, backup *velerov1api.Backup) []error {
|
||||
if r.resticMgr == nil {
|
||||
if r.repoMgr == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -449,7 +450,7 @@ func (r *backupDeletionReconciler) deleteResticSnapshots(ctx context.Context, ba
|
|||
|
||||
var errs []error
|
||||
for _, snapshot := range snapshots {
|
||||
if err := r.resticMgr.Forget(ctx2, snapshot); err != nil {
|
||||
if err := r.repoMgr.Forget(ctx2, snapshot); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/repository"
|
||||
repoconfig "github.com/vmware-tanzu/velero/pkg/repository/config"
|
||||
"github.com/vmware-tanzu/velero/pkg/restic"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/kube"
|
||||
|
@ -45,11 +46,11 @@ type ResticRepoReconciler struct {
|
|||
logger logrus.FieldLogger
|
||||
clock clock.Clock
|
||||
defaultMaintenanceFrequency time.Duration
|
||||
repositoryManager restic.RepositoryManager
|
||||
repositoryManager repository.Manager
|
||||
}
|
||||
|
||||
func NewResticRepoReconciler(namespace string, logger logrus.FieldLogger, client client.Client,
|
||||
defaultMaintenanceFrequency time.Duration, repositoryManager restic.RepositoryManager) *ResticRepoReconciler {
|
||||
defaultMaintenanceFrequency time.Duration, repositoryManager repository.Manager) *ResticRepoReconciler {
|
||||
c := &ResticRepoReconciler{
|
||||
client,
|
||||
namespace,
|
||||
|
@ -163,7 +164,7 @@ func (r *ResticRepoReconciler) initializeRepo(ctx context.Context, req *velerov1
|
|||
// ensureRepo checks to see if a repository exists, and attempts to initialize it if
|
||||
// it does not exist. An error is returned if the repository can't be connected to
|
||||
// or initialized.
|
||||
func ensureRepo(repo *velerov1api.BackupRepository, repoManager restic.RepositoryManager) error {
|
||||
func ensureRepo(repo *velerov1api.BackupRepository, repoManager repository.Manager) error {
|
||||
if err := repoManager.ConnectToRepo(repo); err != nil {
|
||||
// If the repository has not yet been initialized, the error message will always include
|
||||
// the following string. This is the only scenario where we should try to initialize it.
|
||||
|
|
|
@ -24,14 +24,14 @@ import (
|
|||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
resticmokes "github.com/vmware-tanzu/velero/pkg/restic/mocks"
|
||||
repomokes "github.com/vmware-tanzu/velero/pkg/repository/mocks"
|
||||
velerotest "github.com/vmware-tanzu/velero/pkg/test"
|
||||
)
|
||||
|
||||
const defaultMaintenanceFrequency = 10 * time.Minute
|
||||
|
||||
func mockResticRepoReconciler(t *testing.T, rr *velerov1api.BackupRepository, mockOn string, arg interface{}, ret interface{}) *ResticRepoReconciler {
|
||||
mgr := &resticmokes.RepositoryManager{}
|
||||
mgr := &repomokes.RepositoryManager{}
|
||||
if mockOn != "" {
|
||||
mgr.On(mockOn, arg).Return(ret)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
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 repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/vmware-tanzu/velero/internal/credentials"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/repository/provider"
|
||||
"github.com/vmware-tanzu/velero/pkg/restic"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/filesystem"
|
||||
)
|
||||
|
||||
// Manager manages backup repositories.
|
||||
type Manager interface {
|
||||
// InitRepo initializes a repo with the specified name and identifier.
|
||||
InitRepo(repo *velerov1api.BackupRepository) error
|
||||
|
||||
// ConnectToRepo runs the 'restic snapshots' command against the
|
||||
// specified repo, and returns an error if it fails. This is
|
||||
// intended to be used to ensure that the repo exists/can be
|
||||
// authenticated to.
|
||||
ConnectToRepo(repo *velerov1api.BackupRepository) error
|
||||
|
||||
// PruneRepo deletes unused data from a repo.
|
||||
PruneRepo(repo *velerov1api.BackupRepository) error
|
||||
|
||||
// UnlockRepo removes stale locks from a repo.
|
||||
UnlockRepo(repo *velerov1api.BackupRepository) error
|
||||
|
||||
// Forget removes a snapshot from the list of
|
||||
// available snapshots in a repo.
|
||||
Forget(context.Context, restic.SnapshotIdentifier) error
|
||||
}
|
||||
|
||||
type manager struct {
|
||||
namespace string
|
||||
providers map[string]provider.Provider
|
||||
client client.Client
|
||||
repoLocker *RepoLocker
|
||||
repoEnsurer *RepositoryEnsurer
|
||||
fileSystem filesystem.Interface
|
||||
log logrus.FieldLogger
|
||||
}
|
||||
|
||||
// NewManager create a new repository manager.
|
||||
func NewManager(
|
||||
namespace string,
|
||||
client client.Client,
|
||||
repoLocker *RepoLocker,
|
||||
repoEnsurer *RepositoryEnsurer,
|
||||
credentialFileStore credentials.FileStore,
|
||||
log logrus.FieldLogger,
|
||||
) Manager {
|
||||
mgr := &manager{
|
||||
namespace: namespace,
|
||||
client: client,
|
||||
providers: map[string]provider.Provider{},
|
||||
repoLocker: repoLocker,
|
||||
repoEnsurer: repoEnsurer,
|
||||
fileSystem: filesystem.NewFileSystem(),
|
||||
log: log,
|
||||
}
|
||||
|
||||
mgr.providers[velerov1api.BackupRepositoryTypeRestic] = provider.NewResticRepositoryProvider(credentialFileStore, mgr.fileSystem, mgr.log)
|
||||
|
||||
return mgr
|
||||
}
|
||||
|
||||
func (m *manager) InitRepo(repo *velerov1api.BackupRepository) error {
|
||||
m.repoLocker.LockExclusive(repo.Name)
|
||||
defer m.repoLocker.UnlockExclusive(repo.Name)
|
||||
|
||||
prd, err := m.getRepositoryProvider(repo)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
param, err := m.assembleRepoParam(repo)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
return prd.InitRepo(context.Background(), param)
|
||||
}
|
||||
|
||||
func (m *manager) ConnectToRepo(repo *velerov1api.BackupRepository) error {
|
||||
m.repoLocker.Lock(repo.Name)
|
||||
defer m.repoLocker.Unlock(repo.Name)
|
||||
|
||||
prd, err := m.getRepositoryProvider(repo)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
param, err := m.assembleRepoParam(repo)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
return prd.ConnectToRepo(context.Background(), param)
|
||||
}
|
||||
|
||||
func (m *manager) PruneRepo(repo *velerov1api.BackupRepository) error {
|
||||
m.repoLocker.LockExclusive(repo.Name)
|
||||
defer m.repoLocker.UnlockExclusive(repo.Name)
|
||||
|
||||
prd, err := m.getRepositoryProvider(repo)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
param, err := m.assembleRepoParam(repo)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
return prd.PruneRepo(context.Background(), param)
|
||||
}
|
||||
|
||||
func (m *manager) UnlockRepo(repo *velerov1api.BackupRepository) error {
|
||||
m.repoLocker.Lock(repo.Name)
|
||||
defer m.repoLocker.Unlock(repo.Name)
|
||||
|
||||
prd, err := m.getRepositoryProvider(repo)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
param, err := m.assembleRepoParam(repo)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
return prd.EnsureUnlockRepo(context.Background(), param)
|
||||
}
|
||||
|
||||
func (m *manager) Forget(ctx context.Context, snapshot restic.SnapshotIdentifier) error {
|
||||
repo, err := m.repoEnsurer.EnsureRepo(ctx, m.namespace, snapshot.VolumeNamespace, snapshot.BackupStorageLocation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.repoLocker.LockExclusive(repo.Name)
|
||||
defer m.repoLocker.UnlockExclusive(repo.Name)
|
||||
|
||||
prd, err := m.getRepositoryProvider(repo)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
param, err := m.assembleRepoParam(repo)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
return prd.Forget(context.Background(), snapshot.SnapshotID, param)
|
||||
}
|
||||
|
||||
func (m *manager) getRepositoryProvider(repo *velerov1api.BackupRepository) (provider.Provider, error) {
|
||||
switch repo.Spec.RepositoryType {
|
||||
case "", velerov1api.BackupRepositoryTypeRestic:
|
||||
return m.providers[velerov1api.BackupRepositoryTypeRestic], nil
|
||||
default:
|
||||
return nil, fmt.Errorf("failed to get provider for repository %s", repo.Spec.RepositoryType)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *manager) assembleRepoParam(repo *velerov1api.BackupRepository) (provider.RepoParam, error) {
|
||||
bsl := &velerov1api.BackupStorageLocation{}
|
||||
if err := m.client.Get(context.Background(), client.ObjectKey{m.namespace, repo.Spec.BackupStorageLocation}, bsl); err != nil {
|
||||
return provider.RepoParam{}, errors.WithStack(err)
|
||||
}
|
||||
return provider.RepoParam{
|
||||
BackupLocation: bsl,
|
||||
BackupRepo: repo,
|
||||
}, nil
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
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 repository
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
)
|
||||
|
||||
func TestGetRepositoryProvider(t *testing.T) {
|
||||
mgr := NewManager("", nil, nil, nil, nil, nil).(*manager)
|
||||
repo := &velerov1.BackupRepository{}
|
||||
|
||||
// empty repository type
|
||||
provider, err := mgr.getRepositoryProvider(repo)
|
||||
require.Nil(t, err)
|
||||
assert.NotNil(t, provider)
|
||||
|
||||
// valid repository type
|
||||
repo.Spec.RepositoryType = velerov1.BackupRepositoryTypeRestic
|
||||
provider, err = mgr.getRepositoryProvider(repo)
|
||||
require.Nil(t, err)
|
||||
assert.NotNil(t, provider)
|
||||
|
||||
// invalid repository type
|
||||
repo.Spec.RepositoryType = "unknown"
|
||||
_, err = mgr.getRepositoryProvider(repo)
|
||||
require.NotNil(t, err)
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
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 provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/vmware-tanzu/velero/internal/credentials"
|
||||
"github.com/vmware-tanzu/velero/pkg/repository/restic"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/filesystem"
|
||||
)
|
||||
|
||||
func NewResticRepositoryProvider(store credentials.FileStore, fs filesystem.Interface, log logrus.FieldLogger) Provider {
|
||||
return &resticRepositoryProvider{
|
||||
svc: restic.NewRepositoryService(store, fs, log),
|
||||
}
|
||||
}
|
||||
|
||||
type resticRepositoryProvider struct {
|
||||
svc *restic.RepositoryService
|
||||
}
|
||||
|
||||
func (r *resticRepositoryProvider) InitRepo(ctx context.Context, param RepoParam) error {
|
||||
return r.svc.InitRepo(param.BackupLocation, param.BackupRepo)
|
||||
}
|
||||
|
||||
func (r *resticRepositoryProvider) ConnectToRepo(ctx context.Context, param RepoParam) error {
|
||||
return r.svc.ConnectToRepo(param.BackupLocation, param.BackupRepo)
|
||||
}
|
||||
|
||||
func (r *resticRepositoryProvider) PrepareRepo(ctx context.Context, param RepoParam) error {
|
||||
if err := r.InitRepo(ctx, param); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.ConnectToRepo(ctx, param)
|
||||
}
|
||||
|
||||
func (r *resticRepositoryProvider) PruneRepo(ctx context.Context, param RepoParam) error {
|
||||
return r.svc.PruneRepo(param.BackupLocation, param.BackupRepo)
|
||||
}
|
||||
|
||||
func (r *resticRepositoryProvider) PruneRepoQuick(ctx context.Context, param RepoParam) error {
|
||||
// restic doesn't support this operation
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *resticRepositoryProvider) EnsureUnlockRepo(ctx context.Context, param RepoParam) error {
|
||||
return r.svc.UnlockRepo(param.BackupLocation, param.BackupRepo)
|
||||
}
|
||||
|
||||
func (r *resticRepositoryProvider) Forget(ctx context.Context, snapshotID string, param RepoParam) error {
|
||||
return r.svc.Forget(param.BackupLocation, param.BackupRepo, snapshotID)
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
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 restic
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"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"
|
||||
veleroexec "github.com/vmware-tanzu/velero/pkg/util/exec"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/filesystem"
|
||||
)
|
||||
|
||||
func NewRepositoryService(store credentials.FileStore, fs filesystem.Interface, log logrus.FieldLogger) *RepositoryService {
|
||||
return &RepositoryService{
|
||||
credentialsFileStore: store,
|
||||
fileSystem: fs,
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
type RepositoryService struct {
|
||||
credentialsFileStore credentials.FileStore
|
||||
fileSystem filesystem.Interface
|
||||
log logrus.FieldLogger
|
||||
}
|
||||
|
||||
func (r *RepositoryService) InitRepo(bsl *velerov1api.BackupStorageLocation, repo *velerov1api.BackupRepository) error {
|
||||
return r.exec(restic.InitCommand(repo.Spec.ResticIdentifier), bsl)
|
||||
}
|
||||
|
||||
func (r *RepositoryService) ConnectToRepo(bsl *velerov1api.BackupStorageLocation, repo *velerov1api.BackupRepository) error {
|
||||
snapshotsCmd := restic.SnapshotsCommand(repo.Spec.ResticIdentifier)
|
||||
// use the '--latest=1' flag to minimize the amount of data fetched since
|
||||
// we're just validating that the repo exists and can be authenticated
|
||||
// to.
|
||||
// "--last" is replaced by "--latest=1" in restic v0.12.1
|
||||
snapshotsCmd.ExtraFlags = append(snapshotsCmd.ExtraFlags, "--latest=1")
|
||||
|
||||
return r.exec(snapshotsCmd, bsl)
|
||||
}
|
||||
|
||||
func (r *RepositoryService) PruneRepo(bsl *velerov1api.BackupStorageLocation, repo *velerov1api.BackupRepository) error {
|
||||
return r.exec(restic.PruneCommand(repo.Spec.ResticIdentifier), bsl)
|
||||
}
|
||||
|
||||
func (r *RepositoryService) UnlockRepo(bsl *velerov1api.BackupStorageLocation, repo *velerov1api.BackupRepository) error {
|
||||
return r.exec(restic.UnlockCommand(repo.Spec.ResticIdentifier), bsl)
|
||||
}
|
||||
|
||||
func (r *RepositoryService) Forget(bsl *velerov1api.BackupStorageLocation, repo *velerov1api.BackupRepository, snapshotID string) error {
|
||||
return r.exec(restic.ForgetCommand(repo.Spec.ResticIdentifier, snapshotID), bsl)
|
||||
}
|
||||
|
||||
func (r *RepositoryService) exec(cmd *restic.Command, bsl *velerov1api.BackupStorageLocation) error {
|
||||
file, err := r.credentialsFileStore.Path(repokey.RepoKeySelector())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// ignore error since there's nothing we can do and it's a temp file.
|
||||
defer os.Remove(file)
|
||||
|
||||
cmd.PasswordFile = file
|
||||
|
||||
// if there's a caCert on the ObjectStorage, write it to disk so that it can be passed to restic
|
||||
var caCertFile string
|
||||
if bsl.Spec.ObjectStorage != nil && bsl.Spec.ObjectStorage.CACert != nil {
|
||||
caCertFile, err = restic.TempCACertFile(bsl.Spec.ObjectStorage.CACert, bsl.Name, r.fileSystem)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error creating temp cacert file")
|
||||
}
|
||||
// ignore error since there's nothing we can do and it's a temp file.
|
||||
defer os.Remove(caCertFile)
|
||||
}
|
||||
cmd.CACertFile = caCertFile
|
||||
|
||||
env, err := restic.CmdEnv(bsl, r.credentialsFileStore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.Env = env
|
||||
|
||||
// #4820: restrieve insecureSkipTLSVerify from BSL configuration for
|
||||
// AWS plugin. If nothing is return, that means insecureSkipTLSVerify
|
||||
// is not enable for Restic command.
|
||||
skipTLSRet := restic.GetInsecureSkipTLSVerifyFromBSL(bsl, r.log)
|
||||
if len(skipTLSRet) > 0 {
|
||||
cmd.ExtraFlags = append(cmd.ExtraFlags, skipTLSRet)
|
||||
}
|
||||
|
||||
stdout, stderr, err := veleroexec.RunCommand(cmd.Cmd())
|
||||
r.log.WithFields(logrus.Fields{
|
||||
"repository": cmd.RepoName(),
|
||||
"command": cmd.String(),
|
||||
"stdout": stdout,
|
||||
"stderr": stderr,
|
||||
}).Debugf("Ran restic command")
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error running command=%s, stdout=%s, stderr=%s", cmd.String(), stdout, stderr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -20,9 +20,11 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
@ -49,6 +51,14 @@ const (
|
|||
// DefaultVolumesToRestic specifies whether restic should be used, by default, to
|
||||
// take backup of all pod volumes.
|
||||
DefaultVolumesToRestic = false
|
||||
|
||||
// insecureSkipTLSVerifyKey is the flag in BackupStorageLocation's config
|
||||
// to indicate whether to skip TLS verify to setup insecure HTTPS connection.
|
||||
insecureSkipTLSVerifyKey = "insecureSkipTLSVerify"
|
||||
|
||||
// resticInsecureTLSFlag is the flag for Restic command line to indicate
|
||||
// skip TLS verify on https connection.
|
||||
resticInsecureTLSFlag = "--insecure-tls"
|
||||
)
|
||||
|
||||
// SnapshotIdentifier uniquely identifies a restic snapshot
|
||||
|
@ -176,3 +186,22 @@ func CmdEnv(backupLocation *velerov1api.BackupStorageLocation, credentialFileSto
|
|||
|
||||
return env, nil
|
||||
}
|
||||
|
||||
// GetInsecureSkipTLSVerifyFromBSL get insecureSkipTLSVerify flag from BSL configuration,
|
||||
// Then return --insecure-tls flag with boolean value as result.
|
||||
func GetInsecureSkipTLSVerifyFromBSL(backupLocation *velerov1api.BackupStorageLocation, logger logrus.FieldLogger) string {
|
||||
result := ""
|
||||
|
||||
if backupLocation == nil {
|
||||
logger.Info("bsl is nil. return empty.")
|
||||
return result
|
||||
}
|
||||
|
||||
if insecure, _ := strconv.ParseBool(backupLocation.Spec.Config[insecureSkipTLSVerifyKey]); insecure {
|
||||
logger.Debugf("set --insecure-tls=true for Restic command according to BSL %s config", backupLocation.Name)
|
||||
result = resticInsecureTLSFlag + "=true"
|
||||
return result
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1api "k8s.io/api/core/v1"
|
||||
|
@ -216,3 +217,98 @@ func TestTempCACertFile(t *testing.T) {
|
|||
|
||||
os.Remove(fileName)
|
||||
}
|
||||
|
||||
func TestGetInsecureSkipTLSVerifyFromBSL(t *testing.T) {
|
||||
log := logrus.StandardLogger()
|
||||
tests := []struct {
|
||||
name string
|
||||
backupLocation *velerov1api.BackupStorageLocation
|
||||
logger logrus.FieldLogger
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
"Test with nil BSL. Should return empty string.",
|
||||
nil,
|
||||
log,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"Test BSL with no configuration. Should return empty string.",
|
||||
&velerov1api.BackupStorageLocation{
|
||||
Spec: velerov1api.BackupStorageLocationSpec{
|
||||
Provider: "azure",
|
||||
},
|
||||
},
|
||||
log,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"Test with AWS BSL's insecureSkipTLSVerify set to false.",
|
||||
&velerov1api.BackupStorageLocation{
|
||||
Spec: velerov1api.BackupStorageLocationSpec{
|
||||
Provider: "aws",
|
||||
Config: map[string]string{
|
||||
"insecureSkipTLSVerify": "false",
|
||||
},
|
||||
},
|
||||
},
|
||||
log,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"Test with AWS BSL's insecureSkipTLSVerify set to true.",
|
||||
&velerov1api.BackupStorageLocation{
|
||||
Spec: velerov1api.BackupStorageLocationSpec{
|
||||
Provider: "aws",
|
||||
Config: map[string]string{
|
||||
"insecureSkipTLSVerify": "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
log,
|
||||
"--insecure-tls=true",
|
||||
},
|
||||
{
|
||||
"Test with Azure BSL's insecureSkipTLSVerify set to invalid.",
|
||||
&velerov1api.BackupStorageLocation{
|
||||
Spec: velerov1api.BackupStorageLocationSpec{
|
||||
Provider: "azure",
|
||||
Config: map[string]string{
|
||||
"insecureSkipTLSVerify": "invalid",
|
||||
},
|
||||
},
|
||||
},
|
||||
log,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"Test with GCP without insecureSkipTLSVerify.",
|
||||
&velerov1api.BackupStorageLocation{
|
||||
Spec: velerov1api.BackupStorageLocationSpec{
|
||||
Provider: "gcp",
|
||||
Config: map[string]string{},
|
||||
},
|
||||
},
|
||||
log,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"Test with AWS without config.",
|
||||
&velerov1api.BackupStorageLocation{
|
||||
Spec: velerov1api.BackupStorageLocationSpec{
|
||||
Provider: "aws",
|
||||
},
|
||||
},
|
||||
log,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
res := GetInsecureSkipTLSVerifyFromBSL(test.backupLocation, test.logger)
|
||||
|
||||
assert.Equal(t, test.expected, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
/*
|
||||
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 restic
|
||||
|
||||
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()
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
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 restic
|
||||
|
||||
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)
|
||||
}
|
|
@ -1,269 +0,0 @@
|
|||
/*
|
||||
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 restic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/vmware-tanzu/velero/internal/credentials"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
clientset "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned"
|
||||
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"
|
||||
"github.com/vmware-tanzu/velero/pkg/podvolume"
|
||||
"github.com/vmware-tanzu/velero/pkg/repository"
|
||||
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"
|
||||
)
|
||||
|
||||
// RepositoryManager executes commands against restic repositories.
|
||||
type RepositoryManager interface {
|
||||
// InitRepo initializes a repo with the specified name and identifier.
|
||||
InitRepo(repo *velerov1api.BackupRepository) error
|
||||
|
||||
// ConnectToRepo runs the 'restic snapshots' command against the
|
||||
// specified repo, and returns an error if it fails. This is
|
||||
// intended to be used to ensure that the repo exists/can be
|
||||
// authenticated to.
|
||||
ConnectToRepo(repo *velerov1api.BackupRepository) error
|
||||
|
||||
// PruneRepo deletes unused data from a repo.
|
||||
PruneRepo(repo *velerov1api.BackupRepository) error
|
||||
|
||||
// UnlockRepo removes stale locks from a repo.
|
||||
UnlockRepo(repo *velerov1api.BackupRepository) error
|
||||
|
||||
// Forget removes a snapshot from the list of
|
||||
// available snapshots in a repo.
|
||||
Forget(context.Context, SnapshotIdentifier) error
|
||||
|
||||
podvolume.BackupperFactory
|
||||
|
||||
podvolume.RestorerFactory
|
||||
}
|
||||
|
||||
type repositoryManager struct {
|
||||
namespace string
|
||||
veleroClient clientset.Interface
|
||||
repoLister velerov1listers.BackupRepositoryLister
|
||||
repoInformerSynced cache.InformerSynced
|
||||
kbClient kbclient.Client
|
||||
log logrus.FieldLogger
|
||||
repoLocker *repository.RepoLocker
|
||||
repoEnsurer *repository.RepositoryEnsurer
|
||||
fileSystem filesystem.Interface
|
||||
ctx context.Context
|
||||
pvcClient corev1client.PersistentVolumeClaimsGetter
|
||||
pvClient corev1client.PersistentVolumesGetter
|
||||
credentialsFileStore credentials.FileStore
|
||||
podvolume.BackupperFactory
|
||||
podvolume.RestorerFactory
|
||||
}
|
||||
|
||||
const (
|
||||
// insecureSkipTLSVerifyKey is the flag in BackupStorageLocation's config
|
||||
// to indicate whether to skip TLS verify to setup insecure HTTPS connection.
|
||||
insecureSkipTLSVerifyKey = "insecureSkipTLSVerify"
|
||||
|
||||
// resticInsecureTLSFlag is the flag for Restic command line to indicate
|
||||
// skip TLS verify on https connection.
|
||||
resticInsecureTLSFlag = "--insecure-tls"
|
||||
)
|
||||
|
||||
// NewRepositoryManager constructs a RepositoryManager.
|
||||
func NewRepositoryManager(
|
||||
ctx context.Context,
|
||||
namespace string,
|
||||
veleroClient clientset.Interface,
|
||||
repoInformer velerov1informers.BackupRepositoryInformer,
|
||||
repoClient velerov1client.BackupRepositoriesGetter,
|
||||
kbClient kbclient.Client,
|
||||
pvcClient corev1client.PersistentVolumeClaimsGetter,
|
||||
pvClient corev1client.PersistentVolumesGetter,
|
||||
credentialFileStore credentials.FileStore,
|
||||
log logrus.FieldLogger,
|
||||
) (RepositoryManager, error) {
|
||||
rm := &repositoryManager{
|
||||
namespace: namespace,
|
||||
veleroClient: veleroClient,
|
||||
repoLister: repoInformer.Lister(),
|
||||
repoInformerSynced: repoInformer.Informer().HasSynced,
|
||||
kbClient: kbClient,
|
||||
pvcClient: pvcClient,
|
||||
pvClient: pvClient,
|
||||
credentialsFileStore: credentialFileStore,
|
||||
log: log,
|
||||
ctx: ctx,
|
||||
|
||||
repoLocker: repository.NewRepoLocker(),
|
||||
repoEnsurer: repository.NewRepositoryEnsurer(repoInformer, repoClient, log),
|
||||
fileSystem: filesystem.NewFileSystem(),
|
||||
}
|
||||
rm.BackupperFactory = podvolume.NewBackupperFactory(rm.repoLocker, rm.repoEnsurer, rm.veleroClient, rm.pvcClient,
|
||||
rm.pvClient, rm.repoInformerSynced, rm.log)
|
||||
rm.RestorerFactory = podvolume.NewRestorerFactory(rm.repoLocker, rm.repoEnsurer, rm.veleroClient, rm.pvcClient,
|
||||
rm.repoInformerSynced, rm.log)
|
||||
|
||||
return rm, nil
|
||||
}
|
||||
|
||||
func (rm *repositoryManager) InitRepo(repo *velerov1api.BackupRepository) error {
|
||||
// restic init requires an exclusive lock
|
||||
rm.repoLocker.LockExclusive(repo.Name)
|
||||
defer rm.repoLocker.UnlockExclusive(repo.Name)
|
||||
|
||||
return rm.exec(InitCommand(repo.Spec.ResticIdentifier), repo.Spec.BackupStorageLocation)
|
||||
}
|
||||
|
||||
func (rm *repositoryManager) ConnectToRepo(repo *velerov1api.BackupRepository) error {
|
||||
// restic snapshots requires a non-exclusive lock
|
||||
rm.repoLocker.Lock(repo.Name)
|
||||
defer rm.repoLocker.Unlock(repo.Name)
|
||||
|
||||
snapshotsCmd := SnapshotsCommand(repo.Spec.ResticIdentifier)
|
||||
// use the '--latest=1' flag to minimize the amount of data fetched since
|
||||
// we're just validating that the repo exists and can be authenticated
|
||||
// to.
|
||||
// "--last" is replaced by "--latest=1" in restic v0.12.1
|
||||
snapshotsCmd.ExtraFlags = append(snapshotsCmd.ExtraFlags, "--latest=1")
|
||||
|
||||
return rm.exec(snapshotsCmd, repo.Spec.BackupStorageLocation)
|
||||
}
|
||||
|
||||
func (rm *repositoryManager) PruneRepo(repo *velerov1api.BackupRepository) error {
|
||||
// restic prune requires an exclusive lock
|
||||
rm.repoLocker.LockExclusive(repo.Name)
|
||||
defer rm.repoLocker.UnlockExclusive(repo.Name)
|
||||
|
||||
return rm.exec(PruneCommand(repo.Spec.ResticIdentifier), repo.Spec.BackupStorageLocation)
|
||||
}
|
||||
|
||||
func (rm *repositoryManager) UnlockRepo(repo *velerov1api.BackupRepository) error {
|
||||
// restic unlock requires a non-exclusive lock
|
||||
rm.repoLocker.Lock(repo.Name)
|
||||
defer rm.repoLocker.Unlock(repo.Name)
|
||||
|
||||
return rm.exec(UnlockCommand(repo.Spec.ResticIdentifier), repo.Spec.BackupStorageLocation)
|
||||
}
|
||||
|
||||
func (rm *repositoryManager) Forget(ctx context.Context, snapshot SnapshotIdentifier) error {
|
||||
// We can't wait for this in the constructor, because this informer is coming
|
||||
// from the shared informer factory, which isn't started until *after* the repo
|
||||
// manager is instantiated & passed to the controller constructors. We'd get a
|
||||
// deadlock if we tried to wait for this in the constructor.
|
||||
if !cache.WaitForCacheSync(ctx.Done(), rm.repoInformerSynced) {
|
||||
return errors.New("timed out waiting for cache to sync")
|
||||
}
|
||||
|
||||
repo, err := rm.repoEnsurer.EnsureRepo(ctx, rm.namespace, snapshot.VolumeNamespace, snapshot.BackupStorageLocation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// restic forget requires an exclusive lock
|
||||
rm.repoLocker.LockExclusive(repo.Name)
|
||||
defer rm.repoLocker.UnlockExclusive(repo.Name)
|
||||
|
||||
return rm.exec(ForgetCommand(repo.Spec.ResticIdentifier, snapshot.SnapshotID), repo.Spec.BackupStorageLocation)
|
||||
}
|
||||
|
||||
func (rm *repositoryManager) exec(cmd *Command, backupLocation string) error {
|
||||
file, err := rm.credentialsFileStore.Path(repokey.RepoKeySelector())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// ignore error since there's nothing we can do and it's a temp file.
|
||||
defer os.Remove(file)
|
||||
|
||||
cmd.PasswordFile = file
|
||||
|
||||
loc := &velerov1api.BackupStorageLocation{}
|
||||
if err := rm.kbClient.Get(context.Background(), kbclient.ObjectKey{
|
||||
Namespace: rm.namespace,
|
||||
Name: backupLocation,
|
||||
}, loc); err != nil {
|
||||
return errors.Wrap(err, "error getting backup storage location")
|
||||
}
|
||||
|
||||
// if there's a caCert on the ObjectStorage, write it to disk so that it can be passed to restic
|
||||
var caCertFile string
|
||||
if loc.Spec.ObjectStorage != nil && loc.Spec.ObjectStorage.CACert != nil {
|
||||
caCertFile, err = TempCACertFile(loc.Spec.ObjectStorage.CACert, backupLocation, rm.fileSystem)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error creating temp cacert file")
|
||||
}
|
||||
// ignore error since there's nothing we can do and it's a temp file.
|
||||
defer os.Remove(caCertFile)
|
||||
}
|
||||
cmd.CACertFile = caCertFile
|
||||
|
||||
env, err := CmdEnv(loc, rm.credentialsFileStore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.Env = env
|
||||
|
||||
// #4820: restrieve insecureSkipTLSVerify from BSL configuration for
|
||||
// AWS plugin. If nothing is return, that means insecureSkipTLSVerify
|
||||
// is not enable for Restic command.
|
||||
skipTLSRet := GetInsecureSkipTLSVerifyFromBSL(loc, rm.log)
|
||||
if len(skipTLSRet) > 0 {
|
||||
cmd.ExtraFlags = append(cmd.ExtraFlags, skipTLSRet)
|
||||
}
|
||||
|
||||
stdout, stderr, err := veleroexec.RunCommand(cmd.Cmd())
|
||||
rm.log.WithFields(logrus.Fields{
|
||||
"repository": cmd.RepoName(),
|
||||
"command": cmd.String(),
|
||||
"stdout": stdout,
|
||||
"stderr": stderr,
|
||||
}).Debugf("Ran restic command")
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error running command=%s, stdout=%s, stderr=%s", cmd.String(), stdout, stderr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetInsecureSkipTLSVerifyFromBSL get insecureSkipTLSVerify flag from BSL configuration,
|
||||
// Then return --insecure-tls flag with boolean value as result.
|
||||
func GetInsecureSkipTLSVerifyFromBSL(backupLocation *velerov1api.BackupStorageLocation, logger logrus.FieldLogger) string {
|
||||
result := ""
|
||||
|
||||
if backupLocation == nil {
|
||||
logger.Info("bsl is nil. return empty.")
|
||||
return result
|
||||
}
|
||||
|
||||
if insecure, _ := strconv.ParseBool(backupLocation.Spec.Config[insecureSkipTLSVerifyKey]); insecure {
|
||||
logger.Debugf("set --insecure-tls=true for Restic command according to BSL %s config", backupLocation.Name)
|
||||
result = resticInsecureTLSFlag + "=true"
|
||||
return result
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
/*
|
||||
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 restic
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
)
|
||||
|
||||
func TestGetInsecureSkipTLSVerifyFromBSL(t *testing.T) {
|
||||
log := logrus.StandardLogger()
|
||||
tests := []struct {
|
||||
name string
|
||||
backupLocation *velerov1api.BackupStorageLocation
|
||||
logger logrus.FieldLogger
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
"Test with nil BSL. Should return empty string.",
|
||||
nil,
|
||||
log,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"Test BSL with no configuration. Should return empty string.",
|
||||
&velerov1api.BackupStorageLocation{
|
||||
Spec: velerov1api.BackupStorageLocationSpec{
|
||||
Provider: "azure",
|
||||
},
|
||||
},
|
||||
log,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"Test with AWS BSL's insecureSkipTLSVerify set to false.",
|
||||
&velerov1api.BackupStorageLocation{
|
||||
Spec: velerov1api.BackupStorageLocationSpec{
|
||||
Provider: "aws",
|
||||
Config: map[string]string{
|
||||
"insecureSkipTLSVerify": "false",
|
||||
},
|
||||
},
|
||||
},
|
||||
log,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"Test with AWS BSL's insecureSkipTLSVerify set to true.",
|
||||
&velerov1api.BackupStorageLocation{
|
||||
Spec: velerov1api.BackupStorageLocationSpec{
|
||||
Provider: "aws",
|
||||
Config: map[string]string{
|
||||
"insecureSkipTLSVerify": "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
log,
|
||||
"--insecure-tls=true",
|
||||
},
|
||||
{
|
||||
"Test with Azure BSL's insecureSkipTLSVerify set to invalid.",
|
||||
&velerov1api.BackupStorageLocation{
|
||||
Spec: velerov1api.BackupStorageLocationSpec{
|
||||
Provider: "azure",
|
||||
Config: map[string]string{
|
||||
"insecureSkipTLSVerify": "invalid",
|
||||
},
|
||||
},
|
||||
},
|
||||
log,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"Test with GCP without insecureSkipTLSVerify.",
|
||||
&velerov1api.BackupStorageLocation{
|
||||
Spec: velerov1api.BackupStorageLocationSpec{
|
||||
Provider: "gcp",
|
||||
Config: map[string]string{},
|
||||
},
|
||||
},
|
||||
log,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"Test with AWS without config.",
|
||||
&velerov1api.BackupStorageLocation{
|
||||
Spec: velerov1api.BackupStorageLocationSpec{
|
||||
Provider: "aws",
|
||||
},
|
||||
},
|
||||
log,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
res := GetInsecureSkipTLSVerifyFromBSL(test.backupLocation, test.logger)
|
||||
|
||||
assert.Equal(t, test.expected, res)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue