Skip to content

Commit

Permalink
Merge pull request #911 from poyaz/bug/fix-restore-single-file-backup…
Browse files Browse the repository at this point in the history
…-command

Bug/fix restore single file backup command
  • Loading branch information
Kidswiss authored Nov 29, 2023
2 parents c5f5b56 + 8a1708d commit edbcbce
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 2 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ coverage/
.cache
_public/
_archive/
tmp/

.github/release-notes.md

Expand Down
27 changes: 27 additions & 0 deletions e2e/definitions/restore/restore-backupcommand.yaml
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion e2e/kind.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
47 changes: 47 additions & 0 deletions e2e/test-08-restore-backupcommand.bats
Original file line number Diff line number Diff line change
@@ -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}"
}
64 changes: 63 additions & 1 deletion restic/cli/restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -221,6 +227,62 @@ 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")

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 i := 1; i < len(stdOutLines); i++ {
if len(stdOutLines[i]) == 0 {
continue
}

node := &fileNode{}
err := json.Unmarshal([]byte(stdOutLines[i]), node)
if err != nil {
return false, err
}
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])
}
Expand Down

0 comments on commit edbcbce

Please sign in to comment.