Skip to content

Commit bec6f08

Browse files
committed
apimachinery/pkg/labels: add SelectorFromSet
While rambling again about how unsafe labels.SelectorFromSet is as it just returns an empty selector that matches everything when it encounters a parsing error, I noticed that we do not even have a safe alternate. This commit fixes that by adding a `ValidatedSelectorFromSet` func that either returns a Selector or an error. It also changes SelectorFromSet to use SelectorFromValidatedSet under the hood, so invalid Sets are send to the server and rejected there, rather than silently doing the wrong thing by using am empty Selector.
1 parent ed00f42 commit bec6f08

File tree

3 files changed

+56
-10
lines changed

3 files changed

+56
-10
lines changed

staging/src/k8s.io/apimachinery/pkg/labels/labels.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,22 @@ func (ls Set) Get(label string) string {
5757
return ls[label]
5858
}
5959

60-
// AsSelector converts labels into a selectors.
60+
// AsSelector converts labels into a selectors. It does not
61+
// perform any validation, which means the server will reject
62+
// the request if the Set contains invalid values.
6163
func (ls Set) AsSelector() Selector {
6264
return SelectorFromSet(ls)
6365
}
6466

67+
// AsValidatedSelector converts labels into a selectors.
68+
// The Set is validated client-side, which allows to catch errors early.
69+
func (ls Set) AsValidatedSelector() (Selector, error) {
70+
return ValidatedSelectorFromSet(ls)
71+
}
72+
6573
// AsSelectorPreValidated converts labels into a selector, but
66-
// assumes that labels are already validated and thus don't
67-
// preform any validation.
74+
// assumes that labels are already validated and thus doesn't
75+
// perform any validation.
6876
// According to our measurements this is significantly faster
6977
// in codepaths that matter at high scale.
7078
func (ls Set) AsSelectorPreValidated() Selector {

staging/src/k8s.io/apimachinery/pkg/labels/selector.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -869,23 +869,30 @@ func validateLabelValue(k, v string) error {
869869

870870
// SelectorFromSet returns a Selector which will match exactly the given Set. A
871871
// nil and empty Sets are considered equivalent to Everything().
872+
// It does not perform any validation, which means the server will reject
873+
// the request if the Set contains invalid values.
872874
func SelectorFromSet(ls Set) Selector {
875+
return SelectorFromValidatedSet(ls)
876+
}
877+
878+
// ValidatedSelectorFromSet returns a Selector which will match exactly the given Set. A
879+
// nil and empty Sets are considered equivalent to Everything().
880+
// The Set is validated client-side, which allows to catch errors early.
881+
func ValidatedSelectorFromSet(ls Set) (Selector, error) {
873882
if ls == nil || len(ls) == 0 {
874-
return internalSelector{}
883+
return internalSelector{}, nil
875884
}
876885
requirements := make([]Requirement, 0, len(ls))
877886
for label, value := range ls {
878887
r, err := NewRequirement(label, selection.Equals, []string{value})
879-
if err == nil {
880-
requirements = append(requirements, *r)
881-
} else {
882-
//TODO: double check errors when input comes from serialization?
883-
return internalSelector{}
888+
if err != nil {
889+
return nil, err
884890
}
891+
requirements = append(requirements, *r)
885892
}
886893
// sort to have deterministic string representation
887894
sort.Sort(ByKey(requirements))
888-
return internalSelector(requirements)
895+
return internalSelector(requirements), nil
889896
}
890897

891898
// SelectorFromValidatedSet returns a Selector which will match exactly the given Set.

staging/src/k8s.io/apimachinery/pkg/labels/selector_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package labels
1818

1919
import (
20+
"fmt"
2021
"reflect"
2122
"strings"
2223
"testing"
@@ -708,3 +709,33 @@ func TestRequiresExactMatch(t *testing.T) {
708709
})
709710
}
710711
}
712+
713+
func TestValidatedSelectorFromSet(t *testing.T) {
714+
tests := []struct {
715+
name string
716+
input Set
717+
expectedSelector internalSelector
718+
expectedError error
719+
}{
720+
{
721+
name: "Simple Set, no error",
722+
input: Set{"key": "val"},
723+
expectedSelector: internalSelector([]Requirement{{key: "key", operator: selection.Equals, strValues: []string{"val"}}}),
724+
},
725+
{
726+
name: "Invalid Set, value too long",
727+
input: Set{"Key": "axahm2EJ8Phiephe2eixohbee9eGeiyees1thuozi1xoh0GiuH3diewi8iem7Nui"},
728+
expectedError: fmt.Errorf(`invalid label value: "axahm2EJ8Phiephe2eixohbee9eGeiyees1thuozi1xoh0GiuH3diewi8iem7Nui": at key: "Key": must be no more than 63 characters`),
729+
},
730+
}
731+
732+
for _, tc := range tests {
733+
selector, err := ValidatedSelectorFromSet(tc.input)
734+
if !reflect.DeepEqual(err, tc.expectedError) {
735+
t.Fatalf("expected error %v, got error %v", tc.expectedError, err)
736+
}
737+
if err == nil && !reflect.DeepEqual(selector, tc.expectedSelector) {
738+
t.Errorf("expected selector %v, got selector %v", tc.expectedSelector, selector)
739+
}
740+
}
741+
}

0 commit comments

Comments
 (0)