From 2aa5175594c69894e584a83d8558887a8ba45118 Mon Sep 17 00:00:00 2001 From: Daniel Jiang Date: Wed, 12 Aug 2026 13:53:56 +0800 Subject: [PATCH] Mark the existed resource as skipped during restore (#10243) This commit makes sure the object is marked as "skipped" when there's object with same name exists in the cluster during restore. Otherwise, such object will appeared as "failed" in the "Resource list" in the output of "velero restore describe xxx --details" Signed-off-by: Daniel Jiang --- changelogs/unreleased/10243-reasonerjt | 1 + pkg/restore/restore.go | 4 ++ pkg/restore/restore_test.go | 54 +++++++++++++++++++++++++- 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/10243-reasonerjt diff --git a/changelogs/unreleased/10243-reasonerjt b/changelogs/unreleased/10243-reasonerjt new file mode 100644 index 000000000..d650a70bd --- /dev/null +++ b/changelogs/unreleased/10243-reasonerjt @@ -0,0 +1 @@ +Mark the existed resource as skipped during restore \ No newline at end of file diff --git a/pkg/restore/restore.go b/pkg/restore/restore.go index 336add4de..a6d97d1ec 100644 --- a/pkg/restore/restore.go +++ b/pkg/restore/restore.go @@ -1954,6 +1954,8 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso e := errors.Errorf("could not restore, %s %q already exists. Warning: the in-cluster version is different than the backed-up version", obj.GetKind(), obj.GetName()) warnings.Add(namespace, e) + itemStatus.action = ItemRestoreResultSkipped + ctx.restoredItems[itemKey] = itemStatus // existingResourcePolicy is set as update, attempt patch on the resource and add warning if it fails } else if resourcePolicy == velerov1api.PolicyTypeUpdate { // processing update as existingResourcePolicy @@ -1969,6 +1971,8 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso // Preserved Velero behavior when existingResourcePolicy is not specified by the user e := errors.Errorf("could not restore, %s:%s already exists. Warning: the in-cluster version is different than the backed-up version", obj.GetKind(), obj.GetName()) + itemStatus.action = ItemRestoreResultSkipped + ctx.restoredItems[itemKey] = itemStatus warnings.Add(namespace, e) } } diff --git a/pkg/restore/restore_test.go b/pkg/restore/restore_test.go index 46667b1f9..5b013b2be 100644 --- a/pkg/restore/restore_test.go +++ b/pkg/restore/restore_test.go @@ -1086,6 +1086,7 @@ func TestRestoreItems(t *testing.T) { apiResources []*test.APIResource tarball io.Reader want []*test.APIResource + wantWarnings Result expectedRestoreItems map[itemKey]restoredItemStatus disableInformer bool }{ @@ -1328,6 +1329,52 @@ func TestRestoreItems(t *testing.T) { test.Pods(builder.ForPod("ns-1", "sa-1").ObjectMeta(builder.WithLabels("velero.io/backup-name", "foo", "velero.io/restore-name", "bar")).Result()), }, }, + { + name: "mark item as skipped when pod exists in cluster and is different from backed up one, existing resource policy is none", + restore: defaultRestore().ExistingResourcePolicy("none").Result(), + backup: defaultBackup().Result(), + tarball: test.NewTarWriter(t). + AddItems("pods", builder.ForPod("ns-1", "pod-1").ObjectMeta(builder.WithLabels("app", "backed-up")).Result()). + Done(), + apiResources: []*test.APIResource{ + test.Pods(builder.ForPod("ns-1", "pod-1").ObjectMeta(builder.WithLabels("app", "in-cluster")).Result()), + }, + want: []*test.APIResource{ + test.Pods(builder.ForPod("ns-1", "pod-1").ObjectMeta(builder.WithLabels("app", "in-cluster")).Result()), + }, + wantWarnings: Result{ + Namespaces: map[string][]string{ + "ns-1": {"could not restore, Pod \"pod-1\" already exists. Warning: the in-cluster version is different than the backed-up version"}, + }, + }, + expectedRestoreItems: map[itemKey]restoredItemStatus{ + {resource: "v1/Namespace", namespace: "", name: "ns-1"}: {action: "created", itemExists: true, createdName: "ns-1"}, + {resource: "v1/Pod", namespace: "ns-1", name: "pod-1"}: {action: "skipped", itemExists: true}, + }, + }, + { + name: "mark item as skipped when pod exists in cluster and is different from backed up one, existing resource policy is not specified", + restore: defaultRestore().Result(), + backup: defaultBackup().Result(), + tarball: test.NewTarWriter(t). + AddItems("pods", builder.ForPod("ns-1", "pod-1").ObjectMeta(builder.WithLabels("app", "backed-up")).Result()). + Done(), + apiResources: []*test.APIResource{ + test.Pods(builder.ForPod("ns-1", "pod-1").ObjectMeta(builder.WithLabels("app", "in-cluster")).Result()), + }, + want: []*test.APIResource{ + test.Pods(builder.ForPod("ns-1", "pod-1").ObjectMeta(builder.WithLabels("app", "in-cluster")).Result()), + }, + wantWarnings: Result{ + Namespaces: map[string][]string{ + "ns-1": {"could not restore, Pod:pod-1 already exists. Warning: the in-cluster version is different than the backed-up version"}, + }, + }, + expectedRestoreItems: map[itemKey]restoredItemStatus{ + {resource: "v1/Namespace", namespace: "", name: "ns-1"}: {action: "created", itemExists: true, createdName: "ns-1"}, + {resource: "v1/Pod", namespace: "ns-1", name: "pod-1"}: {action: "skipped", itemExists: true}, + }, + }, { name: "service account secrets and image pull secrets are restored when service account already exists in cluster", restore: defaultRestore().Result(), @@ -1437,7 +1484,12 @@ func TestRestoreItems(t *testing.T) { nil, // volume snapshotter getter ) - assertEmptyResults(t, warnings, errs) + if tc.wantWarnings.IsEmpty() { + assertEmptyResults(t, warnings) + } else { + assertWantErrsOrWarnings(t, tc.wantWarnings, warnings) + } + assertEmptyResults(t, errs) assertRestoredItems(t, h, tc.want) if len(tc.expectedRestoreItems) > 0 { assert.Equal(t, tc.expectedRestoreItems, data.RestoredItems)