From 3daa2157fa19586eb2d308a8238c00803f21d987 Mon Sep 17 00:00:00 2001 From: Ethan Mosbaugh Date: Thu, 5 Dec 2024 14:50:26 -0800 Subject: [PATCH] dont add new annotations and labels if not improved dr --- pkg/handlers/restore.go | 12 +++- pkg/kotsadmsnapshot/backup.go | 83 +++++++---------------- pkg/kotsadmsnapshot/backup_test.go | 105 +++++++++++++++++++---------- pkg/kotsadmsnapshot/restore.go | 36 +++++----- pkg/kotsadmsnapshot/types/types.go | 23 +++++++ pkg/operator/operator.go | 4 +- pkg/snapshot/backup.go | 7 +- pkg/snapshot/restore.go | 38 +++++++---- 8 files changed, 176 insertions(+), 132 deletions(-) diff --git a/pkg/handlers/restore.go b/pkg/handlers/restore.go index 322539282b..47bbd7919f 100644 --- a/pkg/handlers/restore.go +++ b/pkg/handlers/restore.go @@ -47,6 +47,14 @@ func (h *Handler) CreateApplicationRestore(w http.ResponseWriter, r *http.Reques return } + if snapshot.IsInstanceBackup(*backup) && snapshot.GetInstanceBackupType(*backup) != snapshottypes.InstanceBackupTypeLegacy { + err := errors.New("only legacy type instance backups are restorable") + logger.Error(err) + createRestoreResponse.Error = err.Error() + JSON(w, http.StatusInternalServerError, createRestoreResponse) + return + } + appID := backup.Annotations["kots.io/app-id"] sequence, err := strconv.ParseInt(backup.Annotations["kots.io/app-sequence"], 10, 64) if err != nil { @@ -149,7 +157,7 @@ func (h *Handler) RestoreApps(w http.ResponseWriter, r *http.Request) { return } - if backup.Annotations["kots.io/instance"] != "true" { + if backup.Annotations[snapshottypes.InstanceBackupAnnotation] != "true" { err := errors.Errorf("backup %s is not an instance backup", backup.ObjectMeta.Name) logger.Error(err) restoreResponse.Error = err.Error() @@ -244,7 +252,7 @@ func (h *Handler) GetRestoreAppsStatus(w http.ResponseWriter, r *http.Request) { return } - if backup.Annotations["kots.io/instance"] != "true" { + if backup.Annotations[snapshottypes.InstanceBackupAnnotation] != "true" { err := errors.Errorf("backup %s is not an instance backup", backup.ObjectMeta.Name) logger.Error(err) response.Error = err.Error() diff --git a/pkg/kotsadmsnapshot/backup.go b/pkg/kotsadmsnapshot/backup.go index 3701c6afbd..dbe8e98e6a 100644 --- a/pkg/kotsadmsnapshot/backup.go +++ b/pkg/kotsadmsnapshot/backup.go @@ -43,25 +43,6 @@ import ( ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" ) -const ( - // InstanceBackupNameLabel is the label used to store the name of the backup for an instance - // backup. - InstanceBackupNameLabel = "replicated.com/backup-name" - // InstanceBackupTypeAnnotation is the annotation used to store the type of backup for an - // instance backup. - InstanceBackupTypeAnnotation = "replicated.com/backup-type" - // InstanceBackupCountAnnotation is the annotation used to store the expected number of backups - // for an instance backup. - InstanceBackupCountAnnotation = "replicated.com/backup-count" - - // InstanceBackupTypeInfra indicates that the backup is of type infrastructure. - InstanceBackupTypeInfra = "infra" - // InstanceBackupTypeApp indicates that the backup is of type application. - InstanceBackupTypeApp = "app" - // InstanceBackupTypeLegacy indicates that the backup is of type legacy (infra + app). - InstanceBackupTypeLegacy = "legacy" -) - func CreateApplicationBackup(ctx context.Context, a *apptypes.App, isScheduled bool) (*velerov1.Backup, error) { downstreams, err := store.GetStore().ListDownstreamsForApp(a.ID) if err != nil { @@ -307,24 +288,32 @@ func CreateInstanceBackup(ctx context.Context, cluster *downstreamtypes.Downstre // GetBackupName returns the name of the backup from the velero backup object label. func GetBackupName(veleroBackup velerov1.Backup) string { - if val, ok := veleroBackup.GetLabels()[InstanceBackupNameLabel]; ok { + if val, ok := veleroBackup.GetLabels()[types.InstanceBackupNameLabel]; ok { return val } return veleroBackup.GetName() } +// IsInstanceBackup returns true if the backup is an instance backup. +func IsInstanceBackup(veleroBackup velerov1.Backup) bool { + if val, ok := veleroBackup.GetAnnotations()[types.InstanceBackupAnnotation]; ok { + return val == "true" + } + return false +} + // GetInstanceBackupType returns the type of the backup from the velero backup object annotation. func GetInstanceBackupType(veleroBackup velerov1.Backup) string { - if val, ok := veleroBackup.GetAnnotations()[InstanceBackupTypeAnnotation]; ok { + if val, ok := veleroBackup.GetAnnotations()[types.InstanceBackupTypeAnnotation]; ok { return val } - return "" + return types.InstanceBackupTypeLegacy } // GetInstanceBackupCount returns the expected number of backups from the velero backup object // annotation. func GetInstanceBackupCount(veleroBackup velerov1.Backup) int { - if val, ok := veleroBackup.GetAnnotations()[InstanceBackupCountAnnotation]; ok { + if val, ok := veleroBackup.GetAnnotations()[types.InstanceBackupCountAnnotation]; ok { num, _ := strconv.Atoi(val) if num > 0 { return num @@ -505,15 +494,14 @@ func getInfrastructureInstanceBackupSpec(ctx context.Context, k8sClient kubernet if err != nil { return nil, errors.Wrap(err, "failed to add annotations to backup") } - // Add improved disaster recovery annotations and labels - if veleroBackup.Labels == nil { - veleroBackup.Labels = map[string]string{} - } - veleroBackup.Labels[InstanceBackupNameLabel] = metadata.backupName if hasAppBackup { - veleroBackup.Annotations[InstanceBackupTypeAnnotation] = InstanceBackupTypeInfra - } else { - veleroBackup.Annotations[InstanceBackupTypeAnnotation] = InstanceBackupTypeLegacy + // Only add improved disaster recovery annotations and labels if we have an app backup + if veleroBackup.Labels == nil { + veleroBackup.Labels = map[string]string{} + } + veleroBackup.Labels[types.InstanceBackupNameLabel] = metadata.backupName + veleroBackup.Annotations[types.InstanceBackupTypeAnnotation] = types.InstanceBackupTypeInfra + veleroBackup.Annotations[types.InstanceBackupCountAnnotation] = strconv.Itoa(2) } if metadata.ec != nil { @@ -582,8 +570,9 @@ func getAppInstanceBackupSpec(k8sClient kubernetes.Interface, metadata instanceB if appVeleroBackup.Labels == nil { appVeleroBackup.Labels = map[string]string{} } - appVeleroBackup.Labels[InstanceBackupNameLabel] = metadata.backupName - appVeleroBackup.Annotations[InstanceBackupTypeAnnotation] = InstanceBackupTypeApp + appVeleroBackup.Labels[types.InstanceBackupNameLabel] = metadata.backupName + appVeleroBackup.Annotations[types.InstanceBackupTypeAnnotation] = types.InstanceBackupTypeApp + appVeleroBackup.Annotations[types.InstanceBackupCountAnnotation] = strconv.Itoa(2) appVeleroBackup.Spec.StorageLocation = "default" @@ -695,17 +684,12 @@ func appendCommonAnnotations(k8sClient kubernetes.Interface, annotations map[str } marshalledAppVersions := string(b) - numBackups := 1 - if hasAppBackup { - numBackups = 2 - } - if annotations == nil { annotations = make(map[string]string, 0) } annotations["kots.io/snapshot-trigger"] = snapshotTrigger annotations["kots.io/snapshot-requested"] = metadata.backupReqestedAt.Format(time.RFC3339) - annotations["kots.io/instance"] = "true" + annotations[types.InstanceBackupAnnotation] = "true" annotations["kots.io/kotsadm-image"] = kotsadmImage annotations["kots.io/kotsadm-deploy-namespace"] = metadata.kotsadmNamespace annotations["kots.io/apps-sequences"] = marshalledAppSequences @@ -716,9 +700,6 @@ func appendCommonAnnotations(k8sClient kubernetes.Interface, annotations map[str annotations["kots.io/embedded-registry"] = embeddedRegistryHost } - // Add improved disaster recovery annotation labels - annotations[InstanceBackupCountAnnotation] = strconv.Itoa(numBackups) - if metadata.ec != nil { annotations = appendECAnnotations(annotations, *metadata.ec) } @@ -893,7 +874,7 @@ func ListInstanceBackups(ctx context.Context, kotsadmNamespace string) ([]*types for _, veleroBackup := range veleroBackups.Items { // TODO: Enforce version? - if veleroBackup.Annotations["kots.io/instance"] != "true" { + if !IsInstanceBackup(veleroBackup) { continue } @@ -1082,22 +1063,6 @@ func getBackupNameFromPrefix(appSlug string) string { return fmt.Sprintf("%s-%s", backupName, randStr) } -func getBackupsFromName(ctx context.Context, veleroClient veleroclientv1.VeleroV1Interface, veleroNamespace string, backupName string) ([]velerov1.Backup, error) { - // try to get the restore from the backup name label - backups, err := veleroClient.Backups(veleroNamespace).List(ctx, metav1.ListOptions{ - LabelSelector: fmt.Sprintf("%s=%s", InstanceBackupNameLabel, velerolabel.GetValidName(backupName)), - }) - if len(backups.Items) > 0 { - return backups.Items, nil - } - backup, err := veleroClient.Backups(veleroNamespace).Get(ctx, backupName, metav1.GetOptions{}) - if err != nil { - return nil, errors.Wrap(err, "failed to get restore") - } - - return []velerov1.Backup{*backup}, nil -} - func DeleteBackup(ctx context.Context, kotsadmNamespace string, backupID string) error { cfg, err := k8sutil.GetClusterConfig() if err != nil { diff --git a/pkg/kotsadmsnapshot/backup_test.go b/pkg/kotsadmsnapshot/backup_test.go index 3ef6597ab6..f289067aa7 100644 --- a/pkg/kotsadmsnapshot/backup_test.go +++ b/pkg/kotsadmsnapshot/backup_test.go @@ -1022,7 +1022,6 @@ func Test_appendCommonAnnotations(t *testing.T) { "kots.io/kotsadm-image": "kotsadm/kotsadm:1.0.0", "kots.io/snapshot-requested": "2024-01-01T00:00:00Z", "kots.io/snapshot-trigger": "manual", - "replicated.com/backup-count": "1", }, }, { @@ -1087,7 +1086,6 @@ func Test_appendCommonAnnotations(t *testing.T) { "kots.io/kotsadm-image": "kotsadm/kotsadm:1.0.0", "kots.io/snapshot-requested": "2024-01-01T00:00:00Z", "kots.io/snapshot-trigger": "schedule", - "replicated.com/backup-count": "2", "kots.io/embedded-cluster": "true", "kots.io/embedded-cluster-id": "embedded-cluster-id", "kots.io/embedded-cluster-version": "embedded-cluster-version", @@ -2090,37 +2088,6 @@ func Test_getInfrastructureInstanceBackupSpec(t *testing.T) { args args assert func(t *testing.T, got *velerov1.Backup, err error) }{ - { - name: "should append backup name label", - setup: func(t *testing.T, mockStore *mock_store.MockStore) { - mockStoreExpectApp1(mockStore) - }, - args: args{ - k8sClient: fake.NewSimpleClientset(kotsadmSts, registryCredsSecret), - metadata: instanceBackupMetadata{ - backupName: "app-1-17332487841234", - backupReqestedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), - kotsadmNamespace: "kotsadm", - backupStorageLocationNamespace: "kotsadm-backups", - apps: map[string]appInstanceBackupMetadata{ - "app-1": { - app: app1, - kotsKinds: kotsKinds, - parentSequence: 1, - }, - }, - isScheduled: true, - ec: nil, - }, - hasAppBackup: false, - }, - assert: func(t *testing.T, got *velerov1.Backup, err error) { - require.NoError(t, err) - if assert.Contains(t, got.Labels, "replicated.com/backup-name") { - assert.Equal(t, "app-1-17332487841234", got.Labels["replicated.com/backup-name"]) - } - }, - }, { name: "KOTSADM_TARGET_NAMESPACE should be added to includedNamespaces", setup: func(t *testing.T, mockStore *mock_store.MockStore) { @@ -2293,9 +2260,6 @@ func Test_getInfrastructureInstanceBackupSpec(t *testing.T) { assert: func(t *testing.T, got *velerov1.Backup, err error) { require.NoError(t, err) assert.Contains(t, got.Spec.IncludedNamespaces, "include-namespace-1") - if assert.Contains(t, got.Annotations, "replicated.com/backup-type") { - assert.Equal(t, "legacy", got.Annotations["replicated.com/backup-type"]) - } }, }, { @@ -2326,9 +2290,78 @@ func Test_getInfrastructureInstanceBackupSpec(t *testing.T) { assert: func(t *testing.T, got *velerov1.Backup, err error) { require.NoError(t, err) assert.NotContains(t, got.Spec.IncludedNamespaces, "include-namespace-1") + }, + }, + { + name: "should not add improved dr metadata when not using improved dr", + setup: func(t *testing.T, mockStore *mock_store.MockStore) { + t.Setenv("EMBEDDED_CLUSTER_ID", "embedded-cluster-id") + t.Setenv("EMBEDDED_CLUSTER_VERSION", "embedded-cluster-version") + + mockStoreExpectApp1(mockStore) + }, + args: args{ + k8sClient: fake.NewSimpleClientset(kotsadmSts, registryCredsSecret), + metadata: instanceBackupMetadata{ + backupName: "app-1-17332487841234", + backupReqestedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + kotsadmNamespace: "kotsadm", + backupStorageLocationNamespace: "kotsadm-backups", + apps: map[string]appInstanceBackupMetadata{ + "app-1": { + app: app1, + kotsKinds: kotsKinds, + parentSequence: 1, + }, + }, + isScheduled: true, + ec: ecMeta, + }, + hasAppBackup: false, + }, + assert: func(t *testing.T, got *velerov1.Backup, err error) { + require.NoError(t, err) + assert.NotContains(t, got.Labels, "replicated.com/backup-name") + assert.NotContains(t, got.Annotations, "replicated.com/backup-type") + assert.NotContains(t, got.Annotations, "replicated.com/backup-count") + }, + }, + { + name: "should add improved dr metadata when not using improved dr", + setup: func(t *testing.T, mockStore *mock_store.MockStore) { + t.Setenv("EMBEDDED_CLUSTER_ID", "embedded-cluster-id") + t.Setenv("EMBEDDED_CLUSTER_VERSION", "embedded-cluster-version") + }, + args: args{ + k8sClient: fake.NewSimpleClientset(kotsadmSts, registryCredsSecret), + metadata: instanceBackupMetadata{ + backupName: "app-1-17332487841234", + backupReqestedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + kotsadmNamespace: "kotsadm", + backupStorageLocationNamespace: "kotsadm-backups", + apps: map[string]appInstanceBackupMetadata{ + "app-1": { + app: app1, + kotsKinds: kotsKinds, + parentSequence: 1, + }, + }, + isScheduled: true, + ec: ecMeta, + }, + hasAppBackup: true, + }, + assert: func(t *testing.T, got *velerov1.Backup, err error) { + require.NoError(t, err) + if assert.Contains(t, got.Labels, "replicated.com/backup-name") { + assert.Equal(t, "app-1-17332487841234", got.Labels["replicated.com/backup-name"]) + } if assert.Contains(t, got.Annotations, "replicated.com/backup-type") { assert.Equal(t, "infra", got.Annotations["replicated.com/backup-type"]) } + if assert.Contains(t, got.Annotations, "replicated.com/backup-count") { + assert.Equal(t, "2", got.Annotations["replicated.com/backup-count"]) + } }, }, { diff --git a/pkg/kotsadmsnapshot/restore.go b/pkg/kotsadmsnapshot/restore.go index 97c1d20f0c..6fe82be98e 100644 --- a/pkg/kotsadmsnapshot/restore.go +++ b/pkg/kotsadmsnapshot/restore.go @@ -16,14 +16,13 @@ import ( velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" veleroclientv1 "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1" velerolabel "github.com/vmware-tanzu/velero/pkg/label" - "go.uber.org/zap" kuberneteserrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/utils/pointer" ) -func GetRestore(ctx context.Context, kotsadmNamespace string, snapshotName string) (*velerov1.Restore, error) { +func GetRestore(ctx context.Context, kotsadmNamespace string, restoreID string) (*velerov1.Restore, error) { cfg, err := k8sutil.GetClusterConfig() if err != nil { return nil, errors.Wrap(err, "failed to get cluster config") @@ -49,7 +48,7 @@ func GetRestore(ctx context.Context, kotsadmNamespace string, snapshotName strin veleroNamespace := bsl.Namespace - restore, err := veleroClient.Restores(veleroNamespace).Get(ctx, snapshotName, metav1.GetOptions{}) + restore, err := veleroClient.Restores(veleroNamespace).Get(ctx, restoreID, metav1.GetOptions{}) if err != nil { if kuberneteserrors.IsNotFound(err) { return nil, nil @@ -60,11 +59,10 @@ func GetRestore(ctx context.Context, kotsadmNamespace string, snapshotName strin return restore, nil } -func CreateApplicationRestore(ctx context.Context, kotsadmNamespace string, snapshotName string, appSlug string) error { +func CreateApplicationRestore(ctx context.Context, kotsadmNamespace string, backupID string, appSlug string) error { // Reference https://github.com/vmware-tanzu/velero/blob/42b612645863c2b3e451b447f9bf798295dd7dba/pkg/cmd/cli/restore/create.go#L222 - logger.Debug("creating restore", - zap.String("snapshotName", snapshotName)) + logger.Debugf("Creating restore for backup %s", backupID) cfg, err := k8sutil.GetClusterConfig() if err != nil { @@ -92,7 +90,7 @@ func CreateApplicationRestore(ctx context.Context, kotsadmNamespace string, snap veleroNamespace := bsl.Namespace // get the backup - backup, err := veleroClient.Backups(veleroNamespace).Get(ctx, snapshotName, metav1.GetOptions{}) + backup, err := veleroClient.Backups(veleroNamespace).Get(ctx, backupID, metav1.GetOptions{}) if err != nil { return errors.Wrap(err, "failed to find backup") } @@ -100,20 +98,24 @@ func CreateApplicationRestore(ctx context.Context, kotsadmNamespace string, snap restore := &velerov1.Restore{ ObjectMeta: metav1.ObjectMeta{ Namespace: veleroNamespace, - Name: snapshotName, // restore name same as snapshot name + Name: backupID, // restore name same as snapshot name }, Spec: velerov1.RestoreSpec{ - BackupName: snapshotName, + BackupName: backupID, RestorePVs: pointer.Bool(true), IncludeClusterResources: pointer.Bool(true), }, } - if backup.Annotations["kots.io/instance"] == "true" { + if IsInstanceBackup(*backup) { + if GetInstanceBackupType(*backup) != types.InstanceBackupTypeLegacy { + return errors.New("only legacy type instance backups are restorable") + } + // only restore app-specific objects - restore.ObjectMeta.Name = fmt.Sprintf("%s.%s", snapshotName, appSlug) + restore.ObjectMeta.Name = fmt.Sprintf("%s.%s", backupID, appSlug) restore.ObjectMeta.Annotations = map[string]string{ - "kots.io/instance": "true", + types.InstanceBackupAnnotation: "true", } restore.Spec.LabelSelector = &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -130,7 +132,7 @@ func CreateApplicationRestore(ctx context.Context, kotsadmNamespace string, snap return nil } -func DeleteRestore(ctx context.Context, kotsadmNamespace string, snapshotName string) error { +func DeleteRestore(ctx context.Context, kotsadmNamespace string, restoreID string) error { cfg, err := k8sutil.GetClusterConfig() if err != nil { return errors.Wrap(err, "failed to get cluster config") @@ -153,15 +155,15 @@ func DeleteRestore(ctx context.Context, kotsadmNamespace string, snapshotName st veleroNamespace := bsl.Namespace - err = veleroClient.Restores(veleroNamespace).Delete(ctx, snapshotName, metav1.DeleteOptions{}) + err = veleroClient.Restores(veleroNamespace).Delete(ctx, restoreID, metav1.DeleteOptions{}) if err != nil && !strings.Contains(err.Error(), "not found") { - return errors.Wrapf(err, "failed to delete restore %s", snapshotName) + return errors.Wrapf(err, "failed to delete restore %s", restoreID) } return nil } -func GetRestoreDetails(ctx context.Context, kotsadmNamespace string, restoreName string) (*types.RestoreDetail, error) { +func GetRestoreDetails(ctx context.Context, kotsadmNamespace string, restoreID string) (*types.RestoreDetail, error) { cfg, err := k8sutil.GetClusterConfig() if err != nil { return nil, errors.Wrap(err, "failed to get cluster config") @@ -187,7 +189,7 @@ func GetRestoreDetails(ctx context.Context, kotsadmNamespace string, restoreName veleroNamespace := backendStorageLocation.Namespace - restore, err := veleroClient.Restores(veleroNamespace).Get(ctx, restoreName, metav1.GetOptions{}) + restore, err := veleroClient.Restores(veleroNamespace).Get(ctx, restoreID, metav1.GetOptions{}) if err != nil { return nil, errors.Wrap(err, "failed to get restore") } diff --git a/pkg/kotsadmsnapshot/types/types.go b/pkg/kotsadmsnapshot/types/types.go index d23cc379d6..7e67ed15ae 100644 --- a/pkg/kotsadmsnapshot/types/types.go +++ b/pkg/kotsadmsnapshot/types/types.go @@ -6,6 +6,29 @@ import ( velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" ) +const ( + // InstanceBackupNameLabel is the label used to store the name of the backup for an instance + // backup. + InstanceBackupNameLabel = "replicated.com/backup-name" + // InstanceBackupTypeAnnotation is the annotation used to store the type of backup for an + // instance backup. + InstanceBackupTypeAnnotation = "replicated.com/backup-type" + // InstanceBackupCountAnnotation is the annotation used to store the expected number of backups + // for an instance backup. + InstanceBackupCountAnnotation = "replicated.com/backup-count" + + // InstanceBackupTypeInfra indicates that the backup is of type infrastructure. + InstanceBackupTypeInfra = "infra" + // InstanceBackupTypeApp indicates that the backup is of type application. + InstanceBackupTypeApp = "app" + // InstanceBackupTypeLegacy indicates that the backup is of type legacy (infra + app). + InstanceBackupTypeLegacy = "legacy" + + // InstanceBackupAnnotation is the annotation used to indicate that a backup is an instance + // backup. + InstanceBackupAnnotation = "kots.io/instance" +) + type App struct { Slug string `json:"slug"` Sequence int64 `json:"sequence"` diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go index 2e750b5ef8..0d414d3321 100644 --- a/pkg/operator/operator.go +++ b/pkg/operator/operator.go @@ -603,7 +603,7 @@ func (o *Operator) handleUndeployCompleted(a *apptypes.App) error { if err != nil { return errors.Wrap(err, "failed to get backup") } - if backup.Annotations["kots.io/instance"] == "true" { + if snapshot.IsInstanceBackup(*backup) { restoreName = fmt.Sprintf("%s.%s", snapshotName, a.Slug) } @@ -643,7 +643,7 @@ func (o *Operator) checkRestoreComplete(a *apptypes.App, restore *velerov1.Resto } var sequence int64 = 0 - if backupAnnotations["kots.io/instance"] == "true" { + if snapshot.IsInstanceBackup(*backup) { b, ok := backupAnnotations["kots.io/apps-sequences"] if !ok || b == "" { return errors.New("instance backup is missing apps sequences annotation") diff --git a/pkg/snapshot/backup.go b/pkg/snapshot/backup.go index 0ef92bfa41..26f47e91b5 100644 --- a/pkg/snapshot/backup.go +++ b/pkg/snapshot/backup.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" "fmt" - "io/ioutil" + "io" "net/http" "os" "time" @@ -12,6 +12,7 @@ import ( "github.com/pkg/errors" "github.com/replicatedhq/kots/pkg/auth" "github.com/replicatedhq/kots/pkg/k8sutil" + snapshottypes "github.com/replicatedhq/kots/pkg/kotsadmsnapshot/types" "github.com/replicatedhq/kots/pkg/logger" velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" veleroclientv1 "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1" @@ -104,7 +105,7 @@ func CreateInstanceBackup(ctx context.Context, options CreateInstanceBackupOptio } defer resp.Body.Close() - respBody, err := ioutil.ReadAll(resp.Body) + respBody, err := io.ReadAll(resp.Body) if err != nil { log.FinishSpinnerWithError() return nil, errors.Wrap(err, "failed to read server response") @@ -174,7 +175,7 @@ func ListInstanceBackups(ctx context.Context, options ListInstanceBackupsOptions backups := []velerov1.Backup{} for _, backup := range b { - if backup.Annotations["kots.io/instance"] != "true" { + if backup.Annotations[snapshottypes.InstanceBackupAnnotation] != "true" { continue } diff --git a/pkg/snapshot/restore.go b/pkg/snapshot/restore.go index af3c438f3b..bb61fed4b9 100644 --- a/pkg/snapshot/restore.go +++ b/pkg/snapshot/restore.go @@ -72,10 +72,14 @@ func RestoreInstanceBackup(ctx context.Context, options RestoreInstanceBackupOpt } // make sure this is an instance backup - if backup.Annotations["kots.io/instance"] != "true" { + if backup.Annotations[snapshottypes.InstanceBackupAnnotation] != "true" { return errors.Wrap(err, "backup provided is not an instance backup") } + if getInstanceBackupType(*backup) != snapshottypes.InstanceBackupTypeLegacy { + return errors.New("only legacy type instance backups are restorable") + } + kotsadmImage, ok := backup.Annotations["kots.io/kotsadm-image"] if !ok { return errors.Wrap(err, "failed to find kotsadm image annotation") @@ -123,15 +127,15 @@ func RestoreInstanceBackup(ctx context.Context, options RestoreInstanceBackupOpt restore := &velerov1.Restore{ ObjectMeta: metav1.ObjectMeta{ Namespace: veleroNamespace, - Name: fmt.Sprintf("%s.kotsadm", options.BackupName), + Name: fmt.Sprintf("%s.kotsadm", backup.Name), Annotations: map[string]string{ - "kots.io/instance": "true", - "kots.io/kotsadm-image": kotsadmImage, - "kots.io/kotsadm-deploy-namespace": kotsadmNamespace, + snapshottypes.InstanceBackupAnnotation: "true", + "kots.io/kotsadm-image": kotsadmImage, + "kots.io/kotsadm-deploy-namespace": kotsadmNamespace, }, }, Spec: velerov1.RestoreSpec{ - BackupName: options.BackupName, + BackupName: backup.Name, LabelSelector: &metav1.LabelSelector{ MatchLabels: map[string]string{ kotsadmtypes.KotsadmKey: kotsadmtypes.KotsadmLabelValue, // restoring applications is in a separate step after kotsadm spins up @@ -200,7 +204,7 @@ func RestoreInstanceBackup(ctx context.Context, options RestoreInstanceBackupOpt } // initiate kotsadm applications restore - err = initiateKotsadmApplicationsRestore(options.BackupName, kotsadmNamespace, kotsadmPodName, log) + err = initiateKotsadmApplicationsRestore(backup.Name, kotsadmNamespace, kotsadmPodName, log) if err != nil { log.FinishSpinnerWithError() return errors.Wrap(err, "failed to restore kotsadm applications") @@ -208,7 +212,7 @@ func RestoreInstanceBackup(ctx context.Context, options RestoreInstanceBackupOpt if options.WaitForApps { // wait for applications restore to finish - err = waitForKotsadmApplicationsRestore(options.BackupName, kotsadmNamespace, kotsadmPodName, log) + err = waitForKotsadmApplicationsRestore(backup.Name, kotsadmNamespace, kotsadmPodName, log) if err != nil { if _, ok := errors.Cause(err).(*kotsadmtypes.ErrorAppsRestore); ok { log.FinishSpinnerWithError() @@ -282,7 +286,7 @@ func ListInstanceRestores(ctx context.Context, options ListInstanceRestoresOptio restores := []velerov1.Restore{} for _, restore := range r.Items { - if restore.Annotations["kots.io/instance"] != "true" { + if restore.Annotations[snapshottypes.InstanceBackupAnnotation] != "true" { continue } @@ -328,7 +332,7 @@ func waitForVeleroRestoreCompleted(ctx context.Context, veleroNamespace string, } } -func initiateKotsadmApplicationsRestore(backupName string, kotsadmNamespace string, kotsadmPodName string, log *logger.CLILogger) error { +func initiateKotsadmApplicationsRestore(backupID string, kotsadmNamespace string, kotsadmPodName string, log *logger.CLILogger) error { getPodName := func() (string, error) { return kotsadmPodName, nil } @@ -361,7 +365,7 @@ func initiateKotsadmApplicationsRestore(backupName string, kotsadmNamespace stri return errors.Wrap(err, "failed to get kotsadm auth slug") } - url := fmt.Sprintf("http://localhost:%d/api/v1/snapshot/%s/restore-apps", localPort, backupName) + url := fmt.Sprintf("http://localhost:%d/api/v1/snapshot/%s/restore-apps", localPort, backupID) requestPayload := map[string]interface{}{ "restoreAll": true, @@ -390,7 +394,7 @@ func initiateKotsadmApplicationsRestore(backupName string, kotsadmNamespace stri return nil } -func waitForKotsadmApplicationsRestore(backupName string, kotsadmNamespace string, kotsadmPodName string, log *logger.CLILogger) error { +func waitForKotsadmApplicationsRestore(backupID string, kotsadmNamespace string, kotsadmPodName string, log *logger.CLILogger) error { getPodName := func() (string, error) { return kotsadmPodName, nil } @@ -423,7 +427,7 @@ func waitForKotsadmApplicationsRestore(backupName string, kotsadmNamespace strin return errors.Wrap(err, "failed to get kotsadm auth slug") } - url := fmt.Sprintf("http://localhost:%d/api/v1/snapshot/%s/apps-restore-status", localPort, backupName) + url := fmt.Sprintf("http://localhost:%d/api/v1/snapshot/%s/apps-restore-status", localPort, backupID) for { requestPayload := map[string]interface{}{ @@ -501,3 +505,11 @@ func waitForKotsadmApplicationsRestore(backupName string, kotsadmNamespace strin time.Sleep(time.Second * 2) } } + +// getInstanceBackupType returns the type of the backup from the velero backup object annotation. +func getInstanceBackupType(veleroBackup velerov1.Backup) string { + if val, ok := veleroBackup.GetAnnotations()[snapshottypes.InstanceBackupTypeAnnotation]; ok { + return val + } + return snapshottypes.InstanceBackupTypeLegacy +}