From c2588c9e860bbd025167f35857faa6674989e1fe Mon Sep 17 00:00:00 2001 From: Ethan Mosbaugh Date: Tue, 3 Dec 2024 11:24:58 -0800 Subject: [PATCH] f --- pkg/k8sutil/kotsadm.go | 7 +- pkg/kotsadmsnapshot/backup.go | 93 ++--- pkg/kotsadmsnapshot/backup_test.go | 605 +++++++++++++++++++++++++++++ pkg/snapshot/backup.go | 1 - 4 files changed, 649 insertions(+), 57 deletions(-) diff --git a/pkg/k8sutil/kotsadm.go b/pkg/k8sutil/kotsadm.go index a3b625bf50..606918ae11 100644 --- a/pkg/k8sutil/kotsadm.go +++ b/pkg/k8sutil/kotsadm.go @@ -21,12 +21,7 @@ const ( KotsadmIDConfigMapName = "kotsadm-id" ) -func FindKotsadmImage(namespace string) (string, error) { - clientset, err := GetClientset() - if err != nil { - return "", errors.Wrap(err, "failed to get k8s client set") - } - +func FindKotsadmImage(clientset kubernetes.Interface, namespace string) (string, error) { var containers []corev1.Container if os.Getenv("POD_OWNER_KIND") == "deployment" { kotsadmDeployment, err := clientset.AppsV1().Deployments(namespace).Get(context.TODO(), "kotsadm", metav1.GetOptions{}) diff --git a/pkg/kotsadmsnapshot/backup.go b/pkg/kotsadmsnapshot/backup.go index 01a37bab62..4b2c8bee5a 100644 --- a/pkg/kotsadmsnapshot/backup.go +++ b/pkg/kotsadmsnapshot/backup.go @@ -202,6 +202,7 @@ func CreateApplicationBackup(ctx context.Context, a *apptypes.App, isScheduled b type instanceBackupMetadata struct { backupName string + backupReqestedAt time.Time kotsadmNamespace string backupStorageLocationNamespace string apps map[string]appInstanceBackupMetadata @@ -294,8 +295,10 @@ func GetInstanceBackupsExpected(veleroBackup velerov1.Backup) int { } func getInstanceBackupMetadata(ctx context.Context, k8sClient kubernetes.Interface, veleroClient veleroclientv1.VeleroV1Interface, cluster *downstreamtypes.Downstream, isScheduled bool) (instanceBackupMetadata, error) { + now := time.Now().UTC() metadata := instanceBackupMetadata{ - backupName: fmt.Sprintf("backup-%d", time.Now().UnixNano()), + backupName: fmt.Sprintf("backup-%d", now.UnixNano()), + backupReqestedAt: now, kotsadmNamespace: util.PodNamespace, isScheduled: isScheduled, } @@ -369,11 +372,9 @@ func getInstanceBackupMetadata(ctx context.Context, k8sClient kubernetes.Interfa _ = os.RemoveAll(archiveDir) } - if util.IsEmbeddedCluster() { - metadata.ec, err = getECInstanceBackupMetadata(ctx) - if err != nil { - return metadata, errors.Wrap(err, "failed to get embedded cluster metadata") - } + metadata.ec, err = getECInstanceBackupMetadata(ctx) + if err != nil { + return metadata, errors.Wrap(err, "failed to get embedded cluster metadata") } return metadata, nil @@ -418,7 +419,7 @@ func getInstanceBackupSpec(ctx context.Context, k8sClient kubernetes.Interface, IncludedNamespaces: []string{}, ExcludedNamespaces: []string{}, IncludeClusterResources: ptr.To(true), - OrLabelSelectors: instanceBackupLabelSelectors(util.IsEmbeddedCluster()), + OrLabelSelectors: instanceBackupLabelSelectors(metadata.ec != nil), OrderedResources: map[string]string{}, Hooks: velerov1.BackupHooks{ Resources: []velerov1.BackupResourceHookSpec{}, @@ -452,7 +453,7 @@ func getInstanceBackupSpec(ctx context.Context, k8sClient kubernetes.Interface, for _, appMeta := range metadata.apps { // Don't merge the backup spec if we are using the new improved DR. if !hasAppSpec { - err := mergeAppBackupSpec(veleroBackup, appMeta.kotsKinds, appMeta.app, metadata.kotsadmNamespace) + err := mergeAppBackupSpec(veleroBackup, appMeta, metadata.kotsadmNamespace, metadata.ec != nil) if err != nil { return nil, errors.Wrap(err, "failed to merge app backup spec") } @@ -492,7 +493,7 @@ func getInstanceBackupSpec(ctx context.Context, k8sClient kubernetes.Interface, // getAppInstanceBackup returns a backup spec only if this is Embedded Cluster and the vendor has // defined both a backup and restore custom resource. func getAppInstanceBackupSpec(ctx context.Context, k8sClient kubernetes.Interface, metadata instanceBackupMetadata) (*velerov1.Backup, error) { - if !util.IsEmbeddedCluster() { + if metadata.ec == nil { return nil, nil } @@ -571,47 +572,55 @@ func getAppInstanceBackupSpec(ctx context.Context, k8sClient kubernetes.Interfac // - includedResources // - excludedResources // - labelSelector -func mergeAppBackupSpec(backup *velerov1.Backup, kotsKinds *kotsutil.KotsKinds, app *apptypes.App, kotsadmNamespace string) error { - backupSpec, err := kotsKinds.Marshal("velero.io", "v1", "Backup") +func mergeAppBackupSpec(backup *velerov1.Backup, appMeta appInstanceBackupMetadata, kotsadmNamespace string, isEC bool) error { + backupSpec, err := appMeta.kotsKinds.Marshal("velero.io", "v1", "Backup") if err != nil { return errors.Wrap(err, "failed to get backup spec from kotskinds") } - var kotskindsBackup *velerov1.Backup if backupSpec == "" { - // If this is Embedded Cluster, backups are always enabled and we will use a default - // minimal backup spec - if util.IsEmbeddedCluster() { - kotskindsBackup = getDefaultEmbeddedClusterBackupSpec() - } else { - return nil - } - } else { - renderedBackup, err := helper.RenderAppFile(app, nil, []byte(backupSpec), kotsKinds, kotsadmNamespace) - if err != nil { - return errors.Wrap(err, "failed to render backup") - } - kotskindsBackup, err = kotsutil.LoadBackupFromContents(renderedBackup) - if err != nil { - return errors.Wrap(err, "failed to load backup from contents") + // If this is Embedded Cluster, backups are always enabled and we must include the + // namespace. + if isEC { + backup.Spec.IncludedNamespaces = append(backup.Spec.IncludedNamespaces, appMeta.kotsKinds.KotsApplication.Spec.AdditionalNamespaces...) } + return nil + } + + renderedBackup, err := helper.RenderAppFile(appMeta.app, nil, []byte(backupSpec), appMeta.kotsKinds, kotsadmNamespace) + if err != nil { + return errors.Wrap(err, "failed to render backup") + } + kotskindsBackup, err := kotsutil.LoadBackupFromContents(renderedBackup) + if err != nil { + return errors.Wrap(err, "failed to load backup from contents") } // included namespaces - backup.Spec.IncludedNamespaces = append(backup.Spec.IncludedNamespaces, kotsKinds.KotsApplication.Spec.AdditionalNamespaces...) + backup.Spec.IncludedNamespaces = append(backup.Spec.IncludedNamespaces, appMeta.kotsKinds.KotsApplication.Spec.AdditionalNamespaces...) backup.Spec.IncludedNamespaces = append(backup.Spec.IncludedNamespaces, kotskindsBackup.Spec.IncludedNamespaces...) // excluded namespaces backup.Spec.ExcludedNamespaces = append(backup.Spec.ExcludedNamespaces, kotskindsBackup.Spec.ExcludedNamespaces...) // annotations - for k, v := range kotskindsBackup.Annotations { - backup.Annotations[k] = v + if len(kotskindsBackup.ObjectMeta.Annotations) > 0 { + if backup.Annotations == nil { + backup.Annotations = map[string]string{} + } + for k, v := range kotskindsBackup.ObjectMeta.Annotations { + backup.Annotations[k] = v + } } // ordered resources - for k, v := range kotskindsBackup.Spec.OrderedResources { - backup.Spec.OrderedResources[k] = v + if len(kotskindsBackup.Spec.OrderedResources) > 0 { + if backup.Spec.OrderedResources == nil { + backup.Spec.OrderedResources = map[string]string{} + } + for k, v := range kotskindsBackup.Spec.OrderedResources { + backup.Spec.OrderedResources[k] = v + } } // backup hooks @@ -620,22 +629,8 @@ func mergeAppBackupSpec(backup *velerov1.Backup, kotsKinds *kotsutil.KotsKinds, return nil } -// getDefaultEmbeddedClusterBackupSpec returns a default velero backup spec for an embedded -// cluster using the old DR (no backup and restore custom resource). -func getDefaultEmbeddedClusterBackupSpec() *velerov1.Backup { - return &velerov1.Backup{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "velero.io/v1", - Kind: "Backup", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "backup", - }, - } -} - func appendCommonAnnotations(ctx context.Context, k8sClient kubernetes.Interface, annotations map[string]string, metadata instanceBackupMetadata, hasAppSpec bool) (map[string]string, error) { - kotsadmImage, err := k8sutil.FindKotsadmImage(metadata.kotsadmNamespace) + kotsadmImage, err := k8sutil.FindKotsadmImage(k8sClient, metadata.kotsadmNamespace) if err != nil { return nil, errors.Wrap(err, "failed to find kotsadm image") } @@ -672,13 +667,11 @@ func appendCommonAnnotations(ctx context.Context, k8sClient kubernetes.Interface numBackups = 2 } - now := time.Now() // TODO - if annotations == nil { annotations = make(map[string]string, 0) } annotations["kots.io/snapshot-trigger"] = snapshotTrigger - annotations["kots.io/snapshot-requested"] = now.UTC().Format(time.RFC3339) + annotations["kots.io/snapshot-requested"] = metadata.backupReqestedAt.Format(time.RFC3339) annotations["kots.io/instance"] = "true" annotations["kots.io/kotsadm-image"] = kotsadmImage annotations["kots.io/kotsadm-deploy-namespace"] = metadata.kotsadmNamespace diff --git a/pkg/kotsadmsnapshot/backup_test.go b/pkg/kotsadmsnapshot/backup_test.go index d38cc7bd8e..9b1f69fd18 100644 --- a/pkg/kotsadmsnapshot/backup_test.go +++ b/pkg/kotsadmsnapshot/backup_test.go @@ -3,12 +3,21 @@ package snapshot import ( "context" "testing" + "time" + gomock "github.com/golang/mock/gomock" embeddedclusterv1beta1 "github.com/replicatedhq/embedded-cluster/kinds/apis/v1beta1" + apptypes "github.com/replicatedhq/kots/pkg/app/types" kotsadmtypes "github.com/replicatedhq/kots/pkg/kotsadm/types" + "github.com/replicatedhq/kots/pkg/kotsutil" + registrytypes "github.com/replicatedhq/kots/pkg/registry/types" + "github.com/replicatedhq/kots/pkg/store" + mock_store "github.com/replicatedhq/kots/pkg/store/mock" + kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" kuberneteserrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -859,3 +868,599 @@ func Test_ecIncludedNamespaces(t *testing.T) { }) } } + +func Test_appendCommonAnnotations(t *testing.T) { + scheme := runtime.NewScheme() + corev1.AddToScheme(scheme) + embeddedclusterv1beta1.AddToScheme(scheme) + + kotsadmSts := &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kotsadm", + Namespace: "kotsadm", + }, + Spec: appsv1.StatefulSetSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "kotsadm", + Image: "kotsadm/kotsadm:1.0.0", + }, + }, + }, + }, + }, + } + registryCredsSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "registry-creds", + Namespace: "kotsadm", + }, + Type: corev1.SecretTypeDockerConfigJson, + Data: map[string][]byte{ + ".dockerconfigjson": []byte(`{"auths":{"host":{"username":"kurl","password":"password"}}}`), + }, + } + + type args struct { + k8sClient kubernetes.Interface + annotations map[string]string + metadata instanceBackupMetadata + hasAppSpec bool + } + tests := []struct { + name string + setup func(t *testing.T) + args args + want map[string]string + wantErr bool + }{ + { + name: "cli install, airgap, multiple apps, not scheduled, has ttl", + setup: func(t *testing.T) { + t.Setenv("DISABLE_OUTBOUND_CONNECTIONS", "true") + }, + args: args{ + k8sClient: fake.NewSimpleClientset(kotsadmSts, registryCredsSecret), + annotations: map[string]string{}, + metadata: instanceBackupMetadata{ + backupName: "backup-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: &apptypes.App{}, + kotsKinds: &kotsutil.KotsKinds{ + Installation: kotsv1beta1.Installation{ + Spec: kotsv1beta1.InstallationSpec{ + VersionLabel: "1.0.1", + }, + }, + }, + parentSequence: 1, + }, + "app-2": { + app: &apptypes.App{}, + kotsKinds: &kotsutil.KotsKinds{ + Installation: kotsv1beta1.Installation{ + Spec: kotsv1beta1.InstallationSpec{ + VersionLabel: "1.0.2", + }, + }, + }, + parentSequence: 2, + }, + }, + isScheduled: false, + snapshotTTL: 24 * time.Hour, + ec: nil, + }, + hasAppSpec: false, + }, + want: map[string]string{ + "kots.io/apps-sequences": "{\"app-1\":1,\"app-2\":2}", + "kots.io/apps-versions": "{\"app-1\":\"1.0.1\",\"app-2\":\"1.0.2\"}", + "kots.io/embedded-registry": "host", + "kots.io/instance": "true", + "kots.io/is-airgap": "true", + "kots.io/kotsadm-deploy-namespace": "kotsadm", + "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-name": "backup-17332487841234", + "replicated.com/backups-expected": "1", + }, + }, + { + name: "ec install, scheduled, no ttl, improved dr", + setup: func(t *testing.T) { + t.Setenv("EMBEDDED_CLUSTER_ID", "embedded-cluster-id") + t.Setenv("EMBEDDED_CLUSTER_VERSION", "embedded-cluster-version") + }, + args: args{ + k8sClient: fake.NewSimpleClientset(kotsadmSts, registryCredsSecret), + annotations: map[string]string{}, + metadata: instanceBackupMetadata{ + backupName: "backup-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: &apptypes.App{}, + kotsKinds: &kotsutil.KotsKinds{ + Installation: kotsv1beta1.Installation{ + Spec: kotsv1beta1.InstallationSpec{ + VersionLabel: "1.0.1", + }, + }, + }, + parentSequence: 1, + }, + }, + isScheduled: true, + ec: &ecInstanceBackupMetadata{ + installation: &embeddedclusterv1beta1.Installation{ + Spec: embeddedclusterv1beta1.InstallationSpec{ + HighAvailability: true, + Network: &embeddedclusterv1beta1.NetworkSpec{ + PodCIDR: "10.128.0.0/20", + ServiceCIDR: "10.129.0.0/20", + }, + RuntimeConfig: &embeddedclusterv1beta1.RuntimeConfigSpec{ + DataDir: "/var/lib/ec", + AdminConsole: embeddedclusterv1beta1.AdminConsoleSpec{ + Port: 30001, + }, + LocalArtifactMirror: embeddedclusterv1beta1.LocalArtifactMirrorSpec{ + Port: 50001, + }, + }, + }, + }, + seaweedFSS3ServiceIP: "10.96.0.10", + }, + }, + hasAppSpec: true, + }, + want: map[string]string{ + "kots.io/apps-sequences": "{\"app-1\":1}", + "kots.io/apps-versions": "{\"app-1\":\"1.0.1\"}", + "kots.io/embedded-registry": "host", + "kots.io/instance": "true", + "kots.io/is-airgap": "false", + "kots.io/kotsadm-deploy-namespace": "kotsadm", + "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-name": "backup-17332487841234", + "replicated.com/backups-expected": "2", + "kots.io/embedded-cluster": "true", + "kots.io/embedded-cluster-id": "embedded-cluster-id", + "kots.io/embedded-cluster-version": "embedded-cluster-version", + "kots.io/embedded-cluster-is-ha": "true", + "kots.io/embedded-cluster-pod-cidr": "10.128.0.0/20", + "kots.io/embedded-cluster-service-cidr": "10.129.0.0/20", + "kots.io/embedded-cluster-seaweedfs-s3-ip": "10.96.0.10", + "kots.io/embedded-cluster-admin-console-port": "30001", + "kots.io/embedded-cluster-local-artifact-mirror-port": "50001", + "kots.io/embedded-cluster-data-dir": "/var/lib/ec", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup(t) + } + got, err := appendCommonAnnotations(context.Background(), tt.args.k8sClient, tt.args.annotations, tt.args.metadata, tt.args.hasAppSpec) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_mergeAppBackupSpec(t *testing.T) { + type args struct { + backup *velerov1.Backup + appMeta appInstanceBackupMetadata + kotsadmNamespace string + isEC bool + } + tests := []struct { + name string + setup func(t *testing.T, mockStore *mock_store.MockStore) + args args + want *velerov1.Backup + wantErr bool + }{ + { + name: "no backup spec", + args: args{ + backup: &velerov1.Backup{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "velero.io/v1", + Kind: "Backup", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "", + GenerateName: "instance-", + Annotations: map[string]string{ + "annotation": "true", + }, + }, + Spec: velerov1.BackupSpec{ + StorageLocation: "default", + IncludedNamespaces: []string{"kotsadm"}, + }, + }, + appMeta: appInstanceBackupMetadata{ + app: &apptypes.App{ + ID: "1", + Slug: "app-1", + IsAirgap: true, + }, + kotsKinds: &kotsutil.KotsKinds{ + KotsApplication: kotsv1beta1.Application{ + Spec: kotsv1beta1.ApplicationSpec{ + AdditionalNamespaces: []string{"another-namespace-1", "another-namespace-2"}, + }, + }, + Installation: kotsv1beta1.Installation{ + Spec: kotsv1beta1.InstallationSpec{ + VersionLabel: "1.0.1", + }, + }, + }, + parentSequence: 1, + }, + kotsadmNamespace: "kotsadm", + isEC: false, + }, + want: &velerov1.Backup{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "velero.io/v1", + Kind: "Backup", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "", + GenerateName: "instance-", + Annotations: map[string]string{ + "annotation": "true", + }, + }, + Spec: velerov1.BackupSpec{ + StorageLocation: "default", + IncludedNamespaces: []string{"kotsadm"}, + }, + }, + }, + { + name: "has backup spec", + setup: func(t *testing.T, mockStore *mock_store.MockStore) { + mockStore.EXPECT().GetLatestAppSequence("1", true).Return(int64(1), nil) + mockStore.EXPECT().GetRegistryDetailsForApp("1").Return(registrytypes.RegistrySettings{ + Hostname: "hostname", + Username: "username", + Password: "password", + Namespace: "namespace", + IsReadOnly: true, + }, nil) + }, + args: args{ + backup: &velerov1.Backup{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "velero.io/v1", + Kind: "Backup", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "", + GenerateName: "instance-", + Annotations: map[string]string{ + "annotation": "true", + }, + }, + Spec: velerov1.BackupSpec{ + StorageLocation: "default", + IncludedNamespaces: []string{"kotsadm"}, + }, + }, + appMeta: appInstanceBackupMetadata{ + app: &apptypes.App{ + ID: "1", + Slug: "app-1", + IsAirgap: true, + }, + kotsKinds: &kotsutil.KotsKinds{ + KotsApplication: kotsv1beta1.Application{ + Spec: kotsv1beta1.ApplicationSpec{ + AdditionalNamespaces: []string{"another-namespace-1", "another-namespace-2"}, + }, + }, + Installation: kotsv1beta1.Installation{ + Spec: kotsv1beta1.InstallationSpec{ + VersionLabel: "1.0.1", + }, + }, + Backup: &velerov1.Backup{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "velero.io/v1", + Kind: "Backup", + }, + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "annotation-1": "true", + "annotation-2": "false", + }, + }, + Spec: velerov1.BackupSpec{ + IncludedNamespaces: []string{"include-namespace-1", "include-namespace-2", "template-isairgap-{{repl IsAirgap }}"}, + ExcludedNamespaces: []string{"exclude-namespace-1", "exclude-namespace-2"}, + OrderedResources: map[string]string{ + "resource-1": "true", + "resource-2": "false", + }, + Hooks: velerov1.BackupHooks{ + Resources: []velerov1.BackupResourceHookSpec{ + { + Name: "hook-1", + }, + { + Name: "hook-2", + }, + }, + }, + }, + }, + }, + parentSequence: 1, + }, + kotsadmNamespace: "kotsadm", + isEC: false, + }, + want: &velerov1.Backup{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "velero.io/v1", + Kind: "Backup", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "", + GenerateName: "instance-", + Annotations: map[string]string{ + "annotation": "true", + "annotation-1": "true", + "annotation-2": "false", + }, + }, + Spec: velerov1.BackupSpec{ + StorageLocation: "default", + IncludedNamespaces: []string{"kotsadm", "another-namespace-1", "another-namespace-2", "include-namespace-1", "include-namespace-2", "template-isairgap-true"}, + ExcludedNamespaces: []string{"exclude-namespace-1", "exclude-namespace-2"}, + OrderedResources: map[string]string{ + "resource-1": "true", + "resource-2": "false", + }, + Hooks: velerov1.BackupHooks{ + Resources: []velerov1.BackupResourceHookSpec{ + { + Name: "hook-1", + }, + { + Name: "hook-2", + }, + }, + }, + }, + }, + }, + { + name: "ec, no backup spec", + args: args{ + backup: &velerov1.Backup{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "velero.io/v1", + Kind: "Backup", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "", + GenerateName: "instance-", + Annotations: map[string]string{ + "annotation": "true", + }, + }, + Spec: velerov1.BackupSpec{ + StorageLocation: "default", + IncludedNamespaces: []string{"kotsadm"}, + }, + }, + appMeta: appInstanceBackupMetadata{ + app: &apptypes.App{ + ID: "1", + Slug: "app-1", + IsAirgap: true, + }, + kotsKinds: &kotsutil.KotsKinds{ + KotsApplication: kotsv1beta1.Application{ + Spec: kotsv1beta1.ApplicationSpec{ + AdditionalNamespaces: []string{"another-namespace-1", "another-namespace-2"}, + }, + }, + Installation: kotsv1beta1.Installation{ + Spec: kotsv1beta1.InstallationSpec{ + VersionLabel: "1.0.1", + }, + }, + }, + parentSequence: 1, + }, + kotsadmNamespace: "kotsadm", + isEC: true, + }, + want: &velerov1.Backup{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "velero.io/v1", + Kind: "Backup", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "", + GenerateName: "instance-", + Annotations: map[string]string{ + "annotation": "true", + }, + }, + Spec: velerov1.BackupSpec{ + StorageLocation: "default", + IncludedNamespaces: []string{"kotsadm", "another-namespace-1", "another-namespace-2"}, + }, + }, + }, + { + name: "ec, has backup spec", + setup: func(t *testing.T, mockStore *mock_store.MockStore) { + mockStore.EXPECT().GetLatestAppSequence("1", true).Return(int64(1), nil) + mockStore.EXPECT().GetRegistryDetailsForApp("1").Return(registrytypes.RegistrySettings{ + Hostname: "hostname", + Username: "username", + Password: "password", + Namespace: "namespace", + IsReadOnly: true, + }, nil) + }, + args: args{ + backup: &velerov1.Backup{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "velero.io/v1", + Kind: "Backup", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "", + GenerateName: "instance-", + Annotations: map[string]string{ + "annotation": "true", + }, + }, + Spec: velerov1.BackupSpec{ + StorageLocation: "default", + IncludedNamespaces: []string{"kotsadm"}, + }, + }, + appMeta: appInstanceBackupMetadata{ + app: &apptypes.App{ + ID: "1", + Slug: "app-1", + IsAirgap: true, + }, + kotsKinds: &kotsutil.KotsKinds{ + KotsApplication: kotsv1beta1.Application{ + Spec: kotsv1beta1.ApplicationSpec{ + AdditionalNamespaces: []string{"another-namespace-1", "another-namespace-2"}, + }, + }, + Installation: kotsv1beta1.Installation{ + Spec: kotsv1beta1.InstallationSpec{ + VersionLabel: "1.0.1", + }, + }, + Backup: &velerov1.Backup{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "velero.io/v1", + Kind: "Backup", + }, + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "annotation-1": "true", + "annotation-2": "false", + }, + }, + Spec: velerov1.BackupSpec{ + IncludedNamespaces: []string{"include-namespace-1", "include-namespace-2", "template-isairgap-{{repl IsAirgap }}"}, + ExcludedNamespaces: []string{"exclude-namespace-1", "exclude-namespace-2"}, + OrderedResources: map[string]string{ + "resource-1": "true", + "resource-2": "false", + }, + Hooks: velerov1.BackupHooks{ + Resources: []velerov1.BackupResourceHookSpec{ + { + Name: "hook-1", + }, + { + Name: "hook-2", + }, + }, + }, + }, + }, + }, + parentSequence: 1, + }, + kotsadmNamespace: "kotsadm", + isEC: true, + }, + want: &velerov1.Backup{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "velero.io/v1", + Kind: "Backup", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "", + GenerateName: "instance-", + Annotations: map[string]string{ + "annotation": "true", + "annotation-1": "true", + "annotation-2": "false", + }, + }, + Spec: velerov1.BackupSpec{ + StorageLocation: "default", + IncludedNamespaces: []string{"kotsadm", "another-namespace-1", "another-namespace-2", "include-namespace-1", "include-namespace-2", "template-isairgap-true"}, + ExcludedNamespaces: []string{"exclude-namespace-1", "exclude-namespace-2"}, + OrderedResources: map[string]string{ + "resource-1": "true", + "resource-2": "false", + }, + Hooks: velerov1.BackupHooks{ + Resources: []velerov1.BackupResourceHookSpec{ + { + Name: "hook-1", + }, + { + Name: "hook-2", + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockStore := mock_store.NewMockStore(ctrl) + store.SetStore(mockStore) + + t.Cleanup(func() { + store.SetStore(nil) + }) + + if tt.setup != nil { + tt.setup(t, mockStore) + } + err := mergeAppBackupSpec(tt.args.backup, tt.args.appMeta, tt.args.kotsadmNamespace, tt.args.isEC) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + assert.Equal(t, tt.want, tt.args.backup) + }) + } +} diff --git a/pkg/snapshot/backup.go b/pkg/snapshot/backup.go index 4404f4fb73..0ef92bfa41 100644 --- a/pkg/snapshot/backup.go +++ b/pkg/snapshot/backup.go @@ -166,7 +166,6 @@ func CreateInstanceBackup(ctx context.Context, options CreateInstanceBackupOptio } func ListInstanceBackups(ctx context.Context, options ListInstanceBackupsOptions) ([]velerov1.Backup, error) { - b, err := ListAllBackups(ctx, options) if err != nil { return nil, errors.Wrap(err, "failed to get backup list")