From 4417b965e1ee73c228b273252d8a762540086dd5 Mon Sep 17 00:00:00 2001 From: Pooya Azarpour Date: Sat, 25 Nov 2023 14:42:19 +0330 Subject: [PATCH 1/5] [ADD] Add root project tmp folder in gitignore Signed-off-by: Pooya Azarpour --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2f1d9a4c9..f18f07550 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ coverage/ .cache _public/ _archive/ +tmp/ .github/release-notes.md From 4ff934e38023f39efbfaa94110a797ce989a1665 Mon Sep 17 00:00:00 2001 From: Pooya Azarpour Date: Sat, 25 Nov 2023 14:45:35 +0330 Subject: [PATCH 2/5] [FIX] Fix using in 'yq' command when yq not contain eval flag Signed-off-by: Pooya Azarpour --- e2e/kind.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/kind.mk b/e2e/kind.mk index cb3f2fb8f..901fef148 100644 --- a/e2e/kind.mk +++ b/e2e/kind.mk @@ -29,7 +29,7 @@ $(KIND_KUBECONFIG): $(KIND) # Applies local-path-config.yaml to kind cluster and forces restart of provisioner - can be simplified once https://github.com/kubernetes-sigs/kind/pull/3090 is merged. # This is necessary due to the multi node cluster. Classic k8s hostPath provisioner doesn't permit multi node and sharedFileSystemPath support is only in local-path-provisioner v0.0.23. @kubectl apply -n local-path-storage -f https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.23/deploy/local-path-storage.yaml - @kubectl get cm -n local-path-storage local-path-config -o yaml|yq e '.data."config.json"="{\"nodePathMap\":[],\"sharedFileSystemPath\": \"/tmp/e2e/local-path-provisioner\"}"'|kubectl apply -f - + @kubectl get cm -n local-path-storage local-path-config -o yaml|yq $(yq --help | grep -q eval && echo e) '.data."config.json"="{\"nodePathMap\":[],\"sharedFileSystemPath\": \"/tmp/e2e/local-path-provisioner\"}"'|kubectl apply -f - @kubectl delete po -n local-path-storage --all $(KIND): export GOBIN = $(go_bin) From 31237e9ca8d90ae5c02f7bc2aa9b204961f795dc Mon Sep 17 00:00:00 2001 From: Pooya Azarpour Date: Sat, 25 Nov 2023 14:45:42 +0330 Subject: [PATCH 3/5] [FIX] Fix restore single file snapshot (when using backupcomamnd annotation). Ref: #803 Signed-off-by: Pooya Azarpour --- restic/cli/restore.go | 49 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/restic/cli/restore.go b/restic/cli/restore.go index 34eed2fbe..4a3356aad 100644 --- a/restic/cli/restore.go +++ b/restic/cli/restore.go @@ -141,7 +141,13 @@ func (r *Restic) getLatestSnapshot(snapshotID string, log logr.Logger) (dto.Snap func (r *Restic) folderRestore(restoreDir string, snapshot dto.Snapshot, restoreFilter string, verify bool, log logr.Logger) error { var linkedDir string - if cfg.Config.RestoreTrimPath { + + singleFile, err := r.isRestoreSingleFile(log, snapshot) + if err != nil { + return err + } + + if !singleFile && cfg.Config.RestoreTrimPath { restoreRoot, err := r.linkRestorePaths(snapshot, restoreDir) if err != nil { return err @@ -221,6 +227,47 @@ func (r *Restic) linkRestorePaths(snapshot dto.Snapshot, restoreDir string) (str return restoreRoot, nil } +func (r *Restic) isRestoreSingleFile(log logr.Logger, snapshot dto.Snapshot) (bool, error) { + buf := bytes.Buffer{} + + opts := CommandOptions{ + Path: r.resticPath, + Args: r.globalFlags.ApplyToCommand("ls", "--json", snapshot.ID), + StdOut: &buf, + } + + cmd := NewCommand(r.ctx, log, opts) + cmd.Run() + capturedStdOut := buf.String() + + stdOutLines := strings.Split(capturedStdOut, "\n") + + count := 0 + for _, fileJSON := range stdOutLines { + node := &fileNode{} + err := json.Unmarshal([]byte(fileJSON), node) + if err != nil { + continue + } + if node.Type == "file" { + count++ + } + if node.Type == "dir" { + count = 0 + break + } + if count >= 2 { + break + } + } + + if count == 1 { + return true, nil + } + + return false, nil +} + func (r *Restic) parsePath(paths []string) string { return path.Base(paths[len(paths)-1]) } From c14e099776476f75ac204d2f4feb91d6972a90cb Mon Sep 17 00:00:00 2001 From: Pooya Azarpour Date: Wed, 29 Nov 2023 15:26:27 +0330 Subject: [PATCH 4/5] [CHANGE] Better handling error for json unmarshal when check `isRestoreSingleFile` Signed-off-by: Pooya Azarpour --- restic/cli/restore.go | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/restic/cli/restore.go b/restic/cli/restore.go index 4a3356aad..912bab429 100644 --- a/restic/cli/restore.go +++ b/restic/cli/restore.go @@ -242,12 +242,27 @@ func (r *Restic) isRestoreSingleFile(log logr.Logger, snapshot dto.Snapshot) (bo stdOutLines := strings.Split(capturedStdOut, "\n") + if len(stdOutLines) == 0 { + err := fmt.Errorf("no list exist for snapshot %v", snapshot.ID) + log.Error(err, "the snapshot list is empty") + return false, err + } + + err := json.Unmarshal([]byte(stdOutLines[0]), &dto.Snapshot{}) + if err != nil { + return false, err + } + count := 0 - for _, fileJSON := range stdOutLines { + for i := 1; i < len(stdOutLines); i++ { + if len(stdOutLines[i]) == 0 { + continue + } + node := &fileNode{} - err := json.Unmarshal([]byte(fileJSON), node) + err := json.Unmarshal([]byte(stdOutLines[i]), node) if err != nil { - continue + return false, err } if node.Type == "file" { count++ From 8a1708d3b064c187782ed4aafec79afaf963250b Mon Sep 17 00:00:00 2001 From: Pooya Azarpour Date: Wed, 29 Nov 2023 15:28:05 +0330 Subject: [PATCH 5/5] [ADD] Add e2e test for backupcommand Signed-off-by: Pooya Azarpour --- .../restore/restore-backupcommand.yaml | 27 +++++++++++ e2e/test-08-restore-backupcommand.bats | 47 +++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 e2e/definitions/restore/restore-backupcommand.yaml create mode 100644 e2e/test-08-restore-backupcommand.bats diff --git a/e2e/definitions/restore/restore-backupcommand.yaml b/e2e/definitions/restore/restore-backupcommand.yaml new file mode 100644 index 000000000..b4c85c042 --- /dev/null +++ b/e2e/definitions/restore/restore-backupcommand.yaml @@ -0,0 +1,27 @@ +apiVersion: k8up.io/v1 +kind: Restore +metadata: + name: k8up-restore-backupcommand + namespace: k8up-e2e-subject +spec: + snapshot: $SNAPSHOT_ID + failedJobsHistoryLimit: 1 + successfulJobsHistoryLimit: 1 + restoreMethod: + folder: + claimName: subject-pvc + backend: + repoPasswordSecretRef: + name: backup-repo + key: password + s3: + endpoint: http://minio.minio.svc.cluster.local:9000 + bucket: backup + accessKeyIDSecretRef: + name: backup-credentials + key: username + secretAccessKeySecretRef: + name: backup-credentials + key: password + podSecurityContext: + runAsUser: $ID diff --git a/e2e/test-08-restore-backupcommand.bats b/e2e/test-08-restore-backupcommand.bats new file mode 100644 index 000000000..c4b97252d --- /dev/null +++ b/e2e/test-08-restore-backupcommand.bats @@ -0,0 +1,47 @@ +#!/usr/bin/env bats + +load "lib/utils" +load "lib/detik" +load "lib/k8up" + +# shellcheck disable=SC2034 +DETIK_CLIENT_NAME="kubectl" +# shellcheck disable=SC2034 +DETIK_CLIENT_NAMESPACE="k8up-e2e-subject" +# shellcheck disable=SC2034 +DEBUG_DETIK="true" + +@test "Given a PVC, When creating a Backup of an annotated app, Then Restore stdout dump to PVC" { + expected_content="expected content: $(timestamp)" + expected_filename="expected_filename.txt" + + given_a_running_operator + given_a_clean_ns + given_s3_storage + given_an_annotated_subject "${expected_filename}" "${expected_content}" + + kubectl apply -f definitions/secrets + yq e '.spec.podSecurityContext.runAsUser='$(id -u)'' definitions/backup/backup.yaml | kubectl apply -f - + + try "at most 10 times every 5s to get backup named 'k8up-backup' and verify that '.status.started' is 'true'" + verify_object_value_by_label job 'k8up.io/owned-by=backup_k8up-backup' '.status.active' 1 true + + wait_until backup/k8up-backup completed + + run restic snapshots + + echo "---BEGIN restic snapshots output---" + echo "${output}" | jq . + echo "---END---" + + echo -n "Number of Snapshots >= 1? " + jq -e 'length >= 1' <<< "${output}" # Ensure that there was actually a backup created + + run get_latest_snap_by_path /k8up-e2e-subject-subject-container.txt + + yq e '.spec.snapshot="'${output}'" | .spec.podSecurityContext.runAsUser='$(id -u)'' definitions/restore/restore-backupcommand.yaml | kubectl apply -f - + wait_until restore/k8up-restore-backupcommand completed + verify "'.status.conditions[?(@.type==\"Completed\")].reason' is 'Succeeded' for Restore named 'k8up-restore-backupcommand'" + + expect_file_in_container 'deploy/annotated-subject-deployment' 'subject-container' "/data/k8up-e2e-subject-subject-container.txt" "${expected_content}" +} \ No newline at end of file