From b51a2b20d310c3ad86687bc919fc0f4eb140f529 Mon Sep 17 00:00:00 2001 From: Abhayraj Jaiswal Date: Sat, 5 Sep 2026 18:13:57 +0000 Subject: [PATCH] fix(restore): guard against nil PVC in in-place restore preflight checks Signed-off-by: Abhayraj Jaiswal --- pkg/restore/inplace/preflight.go | 7 +++++++ pkg/restore/inplace/preflight_test.go | 24 ++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/pkg/restore/inplace/preflight.go b/pkg/restore/inplace/preflight.go index 2ad76931d..cc384862b 100644 --- a/pkg/restore/inplace/preflight.go +++ b/pkg/restore/inplace/preflight.go @@ -50,6 +50,10 @@ func CheckPVCNotInUse( pvc *corev1api.PersistentVolumeClaim, restoreUID types.UID, ) error { + if pvc == nil { + return errors.New("pvc cannot be nil") + } + podList := new(corev1api.PodList) if err := cli.List(ctx, podList, &crclient.ListOptions{Namespace: pvc.Namespace}); err != nil { return errors.Wrapf(err, "failed to check whether PVC %s/%s is in use: failed to list pods in namespace %s", pvc.Namespace, pvc.Name, pvc.Namespace) @@ -139,6 +143,9 @@ func gatedByThisRestore(pod *corev1api.Pod, restoreUID types.UID) bool { // bound to a different PV (the documented cross-namespace clone-and-restore // workflow), and when the backed-up PV name is unknown. func CheckPVCBoundToBackedUpPV(existingPVC *corev1api.PersistentVolumeClaim, backedUpPVName, sourceNamespace string) error { + if existingPVC == nil { + return errors.New("existing PVC cannot be nil") + } if existingPVC.Status.Phase != corev1api.ClaimBound { return errors.Errorf("in-place restore pre-flight check failed, skipping volume data restore: PVC %s/%s is not bound (phase %s)", existingPVC.Namespace, existingPVC.Name, existingPVC.Status.Phase) diff --git a/pkg/restore/inplace/preflight_test.go b/pkg/restore/inplace/preflight_test.go index 094a70273..8588951f9 100644 --- a/pkg/restore/inplace/preflight_test.go +++ b/pkg/restore/inplace/preflight_test.go @@ -82,10 +82,16 @@ func TestCheckPVCNotInUse(t *testing.T) { tests := []struct { name string pods []*corev1api.Pod + pvc *corev1api.PersistentVolumeClaim restoreUID types.UID expectPass bool expectMessage []string + expectError string }{ + { + name: "nil PVC returns error", + expectError: "pvc cannot be nil", + }, { name: "no pods, check passes", expectPass: true, @@ -183,11 +189,19 @@ func TestCheckPVCNotInUse(t *testing.T) { } cli := velerotest.NewFakeControllerRuntimeClient(t, objs...) - pvc := &corev1api.PersistentVolumeClaim{ - ObjectMeta: metav1.ObjectMeta{Name: "pvc-1", Namespace: "default"}, + pvc := tc.pvc + if pvc == nil && tc.name != "nil PVC returns error" { + pvc = &corev1api.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{Name: "pvc-1", Namespace: "default"}, + } } err := CheckPVCNotInUse(t.Context(), cli, pvc, tc.restoreUID) + if tc.expectError != "" { + require.Error(t, err) + assert.EqualError(t, err, tc.expectError) + return + } if tc.expectPass { require.NoError(t, err) return @@ -216,6 +230,12 @@ func TestCheckPVCBoundToBackedUpPV(t *testing.T) { backedUpPVName string expectError string }{ + { + name: "nil existing PVC returns error", + existingPVC: nil, + backedUpPVName: "pv-1", + expectError: "existing PVC cannot be nil", + }, { name: "bound to the backed-up PV, check passes", existingPVC: pvc("default", "pv-1", corev1api.ClaimBound),