From 2ec5a4908d04d68b09fdfa9e823fbd04dc92697d Mon Sep 17 00:00:00 2001 From: akrem-chabchoub Date: Thu, 15 Oct 2026 19:07:11 +0200 Subject: [PATCH 1/3] fix(pvc): handle empty selectors in toK8S conversion to avoid issues with dynamic provisioners --- pkg/k8s/persistentvolumeclaim/selector.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/k8s/persistentvolumeclaim/selector.go b/pkg/k8s/persistentvolumeclaim/selector.go index 468bc76d3..33b621178 100644 --- a/pkg/k8s/persistentvolumeclaim/selector.go +++ b/pkg/k8s/persistentvolumeclaim/selector.go @@ -10,8 +10,13 @@ type Selector struct { MatchExpressions LabelSelectorRequirements } -// toK8S converts Selector to Kubernetes client object +// toK8S converts Selector to Kubernetes client object. +// An empty selector is left unset. Dynamic provisioners such as local-path +// reject a claim when spec.selector is present, even if it matches nothing. func (s *Selector) toK8S() *metav1.LabelSelector { + if len(s.MatchLabels) == 0 && len(s.MatchExpressions) == 0 { + return nil + } return &metav1.LabelSelector{ MatchLabels: s.MatchLabels, MatchExpressions: s.MatchExpressions.toK8S(), From f0f7337cba95f16316e77e911c1709b202f03991 Mon Sep 17 00:00:00 2001 From: akrem-chabchoub Date: Thu, 15 Oct 2026 19:25:18 +0200 Subject: [PATCH 2/3] fix(pvc): remove empty selector from PersistentVolumeClaim specs in tests --- pkg/k8s/persistentvolumeclaim/client_test.go | 1 - pkg/k8s/persistentvolumeclaim/persistentvolumeclaim_test.go | 1 - 2 files changed, 2 deletions(-) diff --git a/pkg/k8s/persistentvolumeclaim/client_test.go b/pkg/k8s/persistentvolumeclaim/client_test.go index b94be6f0c..bdf918dce 100644 --- a/pkg/k8s/persistentvolumeclaim/client_test.go +++ b/pkg/k8s/persistentvolumeclaim/client_test.go @@ -34,7 +34,6 @@ func TestSet(t *testing.T) { Labels: map[string]string{"label_1": "label_value_1"}, }, expectedSpec: v1.PersistentVolumeClaimSpec{ - Selector: &metav1.LabelSelector{}, VolumeMode: func() *v1.PersistentVolumeMode { m := v1.PersistentVolumeFilesystem return &m diff --git a/pkg/k8s/persistentvolumeclaim/persistentvolumeclaim_test.go b/pkg/k8s/persistentvolumeclaim/persistentvolumeclaim_test.go index 820fd7ef8..f3e1fbc69 100644 --- a/pkg/k8s/persistentvolumeclaim/persistentvolumeclaim_test.go +++ b/pkg/k8s/persistentvolumeclaim/persistentvolumeclaim_test.go @@ -102,7 +102,6 @@ func TestToK8s(t *testing.T) { expectedPvcs: []v1.PersistentVolumeClaim{ { Spec: v1.PersistentVolumeClaimSpec{ - Selector: &metav1.LabelSelector{}, VolumeMode: func() *v1.PersistentVolumeMode { m := v1.PersistentVolumeBlock return &m From aa5c3532efd59ad56f01addea10500da5d65a803 Mon Sep 17 00:00:00 2001 From: akrem-chabchoub Date: Sat, 24 Oct 2026 16:24:01 +0200 Subject: [PATCH 3/3] fix(pvc): update Selector handling in PersistentVolumeClaim specs and tests --- pkg/k8s/persistentvolumeclaim/client_test.go | 3 +- .../persistentvolumeclaim.go | 2 +- .../persistentvolumeclaim_test.go | 40 ++++++++++++++++++- pkg/k8s/persistentvolumeclaim/selector.go | 4 +- 4 files changed, 43 insertions(+), 6 deletions(-) diff --git a/pkg/k8s/persistentvolumeclaim/client_test.go b/pkg/k8s/persistentvolumeclaim/client_test.go index bdf918dce..144cb6e17 100644 --- a/pkg/k8s/persistentvolumeclaim/client_test.go +++ b/pkg/k8s/persistentvolumeclaim/client_test.go @@ -57,7 +57,6 @@ func TestSet(t *testing.T) { Annotations: map[string]string{"annotation_1": "annotation_value_X", "annotation_2": "annotation_value_2"}, }, expectedSpec: v1.PersistentVolumeClaimSpec{ - Selector: &metav1.LabelSelector{}, VolumeMode: func() *v1.PersistentVolumeMode { m := v1.PersistentVolumeFilesystem return &m @@ -105,7 +104,7 @@ func TestSet(t *testing.T) { } if !reflect.DeepEqual(response, expected) { - t.Errorf("response expected: %q, got: %q", response, expected) + t.Errorf("response expected: %q, got: %q", expected, response) } } else { diff --git a/pkg/k8s/persistentvolumeclaim/persistentvolumeclaim.go b/pkg/k8s/persistentvolumeclaim/persistentvolumeclaim.go index 5ac32349a..d11db1b6d 100644 --- a/pkg/k8s/persistentvolumeclaim/persistentvolumeclaim.go +++ b/pkg/k8s/persistentvolumeclaim/persistentvolumeclaim.go @@ -50,7 +50,7 @@ type Spec struct { AccessModes AccessModes DataSource DataSource RequestStorage string - Selector Selector + Selector *Selector StorageClass string VolumeMode string VolumeName string diff --git a/pkg/k8s/persistentvolumeclaim/persistentvolumeclaim_test.go b/pkg/k8s/persistentvolumeclaim/persistentvolumeclaim_test.go index f3e1fbc69..314b85231 100644 --- a/pkg/k8s/persistentvolumeclaim/persistentvolumeclaim_test.go +++ b/pkg/k8s/persistentvolumeclaim/persistentvolumeclaim_test.go @@ -27,7 +27,7 @@ func TestToK8s(t *testing.T) { Spec: pvc.Spec{ AccessModes: []pvc.AccessMode{"1", "2"}, RequestStorage: "1Gi", - Selector: pvc.Selector{ + Selector: &pvc.Selector{ MatchLabels: map[string]string{"label_1": "label_value_1"}, MatchExpressions: []pvc.LabelSelectorRequirement{ { @@ -112,6 +112,44 @@ func TestToK8s(t *testing.T) { }, }, }, + { + name: "selector_match_expressions_only", + pvcs: pvc.PersistentVolumeClaims{ + { + Spec: pvc.Spec{ + Selector: &pvc.Selector{ + MatchExpressions: []pvc.LabelSelectorRequirement{ + { + Key: "label_1", + Operator: "In", + Values: []string{"label_value_1"}, + }, + }, + }, + }, + }, + }, + expectedPvcs: []v1.PersistentVolumeClaim{ + { + Spec: v1.PersistentVolumeClaimSpec{ + Selector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "label_1", + Operator: "In", + Values: []string{"label_value_1"}, + }, + }, + }, + VolumeMode: func() *v1.PersistentVolumeMode { + m := v1.PersistentVolumeFilesystem + return &m + }(), + StorageClassName: new(""), + }, + }, + }, + }, } for _, test := range testTable { diff --git a/pkg/k8s/persistentvolumeclaim/selector.go b/pkg/k8s/persistentvolumeclaim/selector.go index 33b621178..8e2bbbab2 100644 --- a/pkg/k8s/persistentvolumeclaim/selector.go +++ b/pkg/k8s/persistentvolumeclaim/selector.go @@ -11,10 +11,10 @@ type Selector struct { } // toK8S converts Selector to Kubernetes client object. -// An empty selector is left unset. Dynamic provisioners such as local-path +// A nil selector is left unset. Dynamic provisioners such as local-path // reject a claim when spec.selector is present, even if it matches nothing. func (s *Selector) toK8S() *metav1.LabelSelector { - if len(s.MatchLabels) == 0 && len(s.MatchExpressions) == 0 { + if s == nil { return nil } return &metav1.LabelSelector{