Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions charts/rancher-backup-crd/templates/resourceset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ spec:
excludeResourceNameRegexp:
nullable: true
type: string
fieldSelectors:
additionalProperties:
nullable: true
type: string
nullable: true
type: object
kinds:
items:
nullable: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,29 @@
kindsRegexp: "."
- apiVersion: "v1"
kindsRegexp: "^secrets$"
resourceNameRegexp: "machine-plan$|rke-state$|machine-state$|machine-driver-secret$|machine-provision$|admission-configuration-psact$|^harvesterconfig|^registryconfig-auth|^harvester-cloud-provider-config"
resourceNameRegexp: "machine-driver-secret$|machine-provision$|admission-configuration-psact$|^harvesterconfig|^registryconfig-auth|^harvester-cloud-provider-config"
namespaces:
- "fleet-default"
- apiVersion: "v1"
kindsRegexp: "^configmaps$"
resourceNames:
- "provisioning-log"
namespaceRegexp: "^c-m-"
- apiVersion: "v1"
kindsRegexp: "^secrets$"
namespaces:
- "fleet-default"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not a problem for this PR, but what do we do about clusters in a different namespace than fleet-default? cc @rancher/observation-backup

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah great question - those weren't being backed up before I would assume as well right? at least thats how this current ResourceSet reads.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is indeed a problem and there's an open issue for it here: #574

A workaround is to use the "resources.cattle.io/backup=true" label to make sure the secret is included in the backup.

fieldSelectors:
"type": "rke.cattle.io/machine-plan"
- apiVersion: "v1"
kindsRegexp: "^secrets$"
namespaces:
- "fleet-default"
fieldSelectors:
"type": "rke.cattle.io/cluster-state"
- apiVersion: "v1"
kindsRegexp: "^secrets$"
namespaces:
- "fleet-default"
fieldSelectors:
"type": "rke.cattle.io/machine-state"
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
- apiVersion: "v1"
kindsRegexp: "^secrets$"
resourceNameRegexp: "machine-plan$|rke-state$|machine-state$|machine-driver-secret$|machine-provision$|admission-configuration-psact$|^harvesterconfig|^registryconfig-auth|^harvester-cloud-provider-config"
resourceNameRegexp: "machine-driver-secret$|machine-provision$|admission-configuration-psact$|^harvesterconfig|^registryconfig-auth|^harvester-cloud-provider-config"
namespaces:
- "fleet-default"
- apiVersion: "v1"
kindsRegexp: "^secrets$"
namespaces:
- "fleet-default"
fieldSelectors:
"type": "rke.cattle.io/machine-plan"
- apiVersion: "v1"
kindsRegexp: "^secrets$"
namespaces:
- "fleet-default"
fieldSelectors:
"type": "rke.cattle.io/cluster-state"
- apiVersion: "v1"
kindsRegexp: "^secrets$"
namespaces:
- "fleet-default"
fieldSelectors:
"type": "rke.cattle.io/machine-state"
151 changes: 144 additions & 7 deletions e2e/backup/backup_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package backup_test

import (
"archive/tar"
"bytes"
"compress/gzip"
"context"
"fmt"
"io"
"strings"
"sync"
"time"
Expand All @@ -25,13 +29,14 @@ import (
)

const (
encSecret = "encryption-config"
s3Recurring = "s3-recurring"
s3NonEncryptedBackup = "s3-insecure"
s3EncryptedBackup = "s3-secure"
localNonEncrypedBackup = "local-driver-non-encrypted"
localEncryptedBackup = "local-driver-encrypted"
localCattleDevDriverPath = "../../backups"
encSecret = "encryption-config"
s3Recurring = "s3-recurring"
s3NonEncryptedBackup = "s3-insecure"
s3EncryptedBackup = "s3-secure"
localNonEncrypedBackup = "local-driver-non-encrypted"
localEncryptedBackup = "local-driver-encrypted"
localCustomResourceSetBackup = "local-custom-resource-set-backup"
localCattleDevDriverPath = "../../backups"

insecureBucket = "rancherbackups-insecure"
secureBucket = "rancherbackups-secure"
Expand Down Expand Up @@ -135,6 +140,55 @@ func formatBackupMetadataMetrics(backups []backupv1.Backup) string {
return metrics
}

// extractTarballContents extracts the contents of a tarball from MinIO and returns them as a map of filenames to contents.
func extractTarballContents(minioClient *minio.Client, bucket, key string) (map[string][]byte, error) {
obj, err := minioClient.GetObject(testCtx, bucket, key, minio.GetObjectOptions{})
if err != nil {
return nil, fmt.Errorf("failed to fetch tarball from minio: %v", err)
}
defer obj.Close()

gzr, err := gzip.NewReader(obj)
if err != nil {
return nil, fmt.Errorf("failed to create gzip reader: %v", err)
}
defer gzr.Close()

contents := make(map[string][]byte)
tr := tar.NewReader(gzr)

for {
header, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return nil, fmt.Errorf("failed to read tarball: %v", err)
}

switch header.Typeflag {
case tar.TypeReg:
// file
bb := &bytes.Buffer{}
if _, err := io.Copy(bb, tr); err != nil {
return nil, fmt.Errorf("failed to read file contents: %v", err)
}

contents[header.Name] = bb.Bytes()
case tar.TypeDir:
// directory
contents[header.Name] = nil
case tar.TypeSymlink:
// symlink
contents[header.Name] = []byte(header.Linkname)
default:
return nil, fmt.Errorf("unsupported tarball entry type: %v", header.Typeflag)
}
}

return contents, nil
}

var _ = Describe("Backup e2e remote", Ordered, Label("integration"), func() {
var o *ObjectTracker

Expand All @@ -153,6 +207,9 @@ var _ = Describe("Backup e2e remote", Ordered, Label("integration"), func() {
SetupEncryption(o)
By("deploying minio locally")
minioClient, minioEndpoint = SetupMinio(o)

By("creating custom resourceSet")
SetupCustomResourceSet(testCtx, o, k8sClient)
})

When("we take a non-encrypted backup", func() {
Expand Down Expand Up @@ -514,6 +571,86 @@ var _ = Describe("Backup e2e remote", Ordered, Label("integration"), func() {
}).Should(Succeed())
})
})

When("we take a non-encrypted backup with a custom resource-set", func() {
It("should create a backup CR", func() {
b := &backupv1.Backup{
ObjectMeta: metav1.ObjectMeta{
Name: localCustomResourceSetBackup,
},
Spec: backupv1.BackupSpec{
ResourceSetName: "custom-resource-set",
StorageLocation: &backupv1.StorageLocation{
S3: &backupv1.S3ObjectStore{
CredentialSecretName: credentialSecretName,
CredentialSecretNamespace: ts.ChartNamespace,
BucketName: insecureBucket,
Endpoint: minioEndpoint,
InsecureTLSSkipVerify: true,
},
},
},
}
o.Add(b)

Expect(k8sClient.Create(testCtx, b)).To(Succeed())
Eventually(Object(b)).Should(Exist())
})

Specify("the backup should be successful", func() {
Eventually(func() error {
return isBackupSuccessul(&backupv1.Backup{
ObjectMeta: metav1.ObjectMeta{
Name: localCustomResourceSetBackup,
},
})
}).Should(Succeed())
})

Specify("ensure the backup contains the single secret", func() {
Eventually(func() error {
// Fetch the backup object from the cluster
backup := &backupv1.Backup{}
if err := k8sClient.Get(testCtx, client.ObjectKey{Name: localCustomResourceSetBackup}, backup); err != nil {
return err
}

// Ensure the backup status has a filename
if backup.Status.Filename == "" {
return fmt.Errorf("backup filename is empty")
}

objs := minioClient.ListObjects(testCtx, insecureBucket, minio.ListObjectsOptions{})

// convert the channel to a slice
retObj := make([]minio.ObjectInfo, 2)
for obj := range objs {
retObj = append(retObj, obj)
}

// filter the slice to only include the keys that contain "custom-resource-set-backup"
keys := lo.FilterMap(retObj, func(info minio.ObjectInfo, i int) (string, bool) {
return info.Key, strings.Contains(info.Key, "custom-resource-set-backup")
})
if len(keys) == 0 {
return fmt.Errorf("no appropriate backup found in bucket %s", insecureBucket)
}

contents, err := extractTarballContents(minioClient, insecureBucket, keys[0])
if err != nil {
return fmt.Errorf("failed to extract tarball contents: %s", err)
}

// we backed up the docker-config secret
Expect(contents).To(HaveKey("secrets.#v1/default/docker-config-json.json"))
// but not the regular secret
Expect(contents).NotTo(HaveKey("secrets.#v1/default/regular.json"))

return nil
}).Should(Succeed())
})
})

})

var _ = Describe("Backup e2e local driver", Ordered, Label("integration"), func() {
Expand Down
48 changes: 48 additions & 0 deletions e2e/backup/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"context"
"fmt"

"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/rancher/backup-restore-operator/pkg/util/encryptionconfig"

. "github.com/kralicky/kmatch"
Expand Down Expand Up @@ -118,3 +120,49 @@ func SetupMinio(o *ObjectTracker) (client *minio.Client, minioEndpoint string) {
Eventually(Object(secret)).Should(Exist())
return client, minioEndpoint
}

func SetupCustomResourceSet(ctx context.Context, o *ObjectTracker, k8sClient client.Client) {
// create a resource-set which matches a field selector - in this case only dockerconfigjson
rs := &backupv1.ResourceSet{
ObjectMeta: metav1.ObjectMeta{
Name: "custom-resource-set",
},
ResourceSelectors: []backupv1.ResourceSelector{
{
APIVersion: "v1",
KindsRegexp: "^secrets$",
NamespaceRegexp: "default",
FieldSelectors: map[string]string{
"type": "kubernetes.io/dockerconfigjson",
},
},
},
}
o.Add(rs)
Expect(k8sClient.Create(ctx, rs)).To(Succeed())

// create some secrets to match that resource-set
dockercfg := &corev1.Secret{
Type: corev1.SecretTypeDockerConfigJson,
ObjectMeta: metav1.ObjectMeta{
Name: "docker-config-json",
Namespace: "default",
},
StringData: map[string]string{
".dockerconfigjson": `{"auths":{"https://index.docker.io/v1/":{"username":"foo","password":"bar"}}}`,
},
}
o.Add(dockercfg)
Expect(k8sClient.Create(ctx, dockercfg)).To(Succeed())

opaque := &corev1.Secret{
Type: corev1.SecretTypeOpaque,
ObjectMeta: metav1.ObjectMeta{
Name: "regular",
Namespace: "default",
},
StringData: map[string]string{"some": "data"},
}
o.Add(opaque)
Expect(k8sClient.Create(ctx, opaque)).To(Succeed())
}
2 changes: 2 additions & 0 deletions pkg/apis/resources.cattle.io/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package v1
import (
"github.com/rancher/wrangler/v3/pkg/genericcondition"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
)

var (
Expand Down Expand Up @@ -68,6 +69,7 @@ type ResourceSelector struct {
Namespaces []string `json:"namespaces,omitempty"`
NamespaceRegexp string `json:"namespaceRegexp,omitempty"`
LabelSelectors *metav1.LabelSelector `json:"labelSelectors,omitempty"`
FieldSelectors fields.Set `json:"fieldSelectors,omitempty"`
ExcludeKinds []string `json:"excludeKinds,omitempty"`
ExcludeResourceNameRegexp string `json:"excludeResourceNameRegexp,omitempty"`
}
Expand Down
8 changes: 8 additions & 0 deletions pkg/apis/resources.cattle.io/v1/zz_generated_deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading