blob: 93f1551de6523fd92e2d108b73b2f2fe8c1b7450 [file] [log] [blame]
Serge Bazanski1f789542024-05-22 14:01:50 +02001package node
2
3import (
4 "fmt"
5 "regexp"
Serge Bazanskidd2b80f2024-09-24 13:06:27 +00006 "strings"
7
8 "k8s.io/apimachinery/pkg/util/validation"
Serge Bazanski53458ba2024-06-18 09:56:46 +00009
10 cpb "source.monogon.dev/metropolis/proto/common"
Serge Bazanski1f789542024-05-22 14:01:50 +020011)
12
13var (
14 reLabelFirstLast = regexp.MustCompile(`^[a-zA-Z0-9]$`)
15 reLabelBody = regexp.MustCompile(`^[a-zA-Z0-9\-._]*$`)
16
17 // ErrLabelEmpty is returned by ValidateLabel if the label key/value is not at
18 // least one character long.
19 ErrLabelEmpty = fmt.Errorf("empty")
20 // ErrLabelTooLong is returned by ValidateLabel if the label key/value is more
21 // than 63 characters long.
22 ErrLabelTooLong = fmt.Errorf("too long")
23 // ErrLabelInvalidFirstCharacter is returned by ValidateLabel if the label
24 // key/value contains an invalid character on the first position.
25 ErrLabelInvalidFirstCharacter = fmt.Errorf("first character not a letter or number")
26 // ErrLabelInvalidLastCharacter is returned by ValidateLabel if the label
27 // key/value contains an invalid character on the last position.
28 ErrLabelInvalidLastCharacter = fmt.Errorf("last character not a letter or number")
29 // ErrLabelInvalidCharacter is returned by ValidateLabel if the label key/value
30 // contains an invalid character.
31 ErrLabelInvalidCharacter = fmt.Errorf("invalid character")
Serge Bazanskidd2b80f2024-09-24 13:06:27 +000032 ErrLabelEmptyPrefix = fmt.Errorf("empty prefix")
33 ErrLabelInvalidPrefix = fmt.Errorf("invalid prefix")
Serge Bazanski1f789542024-05-22 14:01:50 +020034)
35
36const (
37 // MaxLabelsPerNode is the absolute maximum of labels that can be attached to a
38 // node.
39 MaxLabelsPerNode = 128
40)
41
Serge Bazanskidd2b80f2024-09-24 13:06:27 +000042func validatePrefix(prefix string) error {
43 if prefix == "" {
44 return ErrLabelEmptyPrefix
45 }
46 if errs := validation.IsDNS1123Subdomain(prefix); len(errs) > 0 {
47 return ErrLabelInvalidPrefix
48 }
49 return nil
50}
51
52// ValidateLabelKey ensures that a given node label key is valid:
Serge Bazanski1f789542024-05-22 14:01:50 +020053//
54// 1. 1 to 63 characters long (inclusive);
55// 2. Characters are all ASCII a-z A-Z 0-9 '_', '-' or '.';
56// 3. The first character is ASCII a-z A-Z or 0-9.
57// 4. The last character is ASCII a-z A-Z or 0-9.
Serge Bazanskidd2b80f2024-09-24 13:06:27 +000058// 5. Optional slash-delimited prefix which contains a valid 'DNS subdomain'.
Serge Bazanski1f789542024-05-22 14:01:50 +020059//
60// If it's valid, nil is returned. Otherwise, one of ErrLabelEmpty,
61// ErrLabelTooLong, ErrLabelInvalidFirstCharacter or ErrLabelInvalidCharacter is
62// returned.
Serge Bazanskidd2b80f2024-09-24 13:06:27 +000063func ValidateLabelKey(v string) error {
64 // Split away prefix.
65 parts := strings.Split(v, "/")
66 switch len(parts) {
67 case 1:
68 case 2:
69 prefix := parts[0]
70 if err := validatePrefix(prefix); err != nil {
71 return err
72 }
73 v = parts[1]
74 default:
75 return ErrLabelInvalidPrefix
76 }
77
Serge Bazanski1f789542024-05-22 14:01:50 +020078 if len(v) == 0 {
79 return ErrLabelEmpty
80 }
Serge Bazanskidd2b80f2024-09-24 13:06:27 +000081
Serge Bazanski1f789542024-05-22 14:01:50 +020082 if len(v) > 63 {
83 return ErrLabelTooLong
84 }
85 if !reLabelFirstLast.MatchString(string(v[0])) {
86 return ErrLabelInvalidFirstCharacter
87 }
88 if !reLabelFirstLast.MatchString(string(v[len(v)-1])) {
89 return ErrLabelInvalidLastCharacter
90 }
91 // Body characters are a superset of the first/last characters, and we've already
92 // checked those so we can check the entire string here.
93 if !reLabelBody.MatchString(v) {
94 return ErrLabelInvalidCharacter
95 }
96 return nil
97}
Serge Bazanski53458ba2024-06-18 09:56:46 +000098
Serge Bazanskidd2b80f2024-09-24 13:06:27 +000099// ValidateLabelValue ensures that a given node label value is valid:
100//
101// 1. 0 to 63 characters long (inclusive);
102// 2. Characters are all ASCII a-z A-Z 0-9 '_', '-' or '.';
103// 3. The first character is ASCII a-z A-Z or 0-9.
104// 4. The last character is ASCII a-z A-Z or 0-9.
105//
106// If it's valid, nil is returned. Otherwise, one of ErrLabelTooLong,
107// ErrLabelInvalidFirstCharacter, or ErrLabelInvalidLastCharacter,
108// ErrLabelInvalidCharacter is returned.
109func ValidateLabelValue(v string) error {
110 if len(v) == 0 {
111 return nil
112 }
113 if len(v) > 63 {
114 return ErrLabelTooLong
115 }
116 if !reLabelFirstLast.MatchString(string(v[0])) {
117 return ErrLabelInvalidFirstCharacter
118 }
119 if !reLabelFirstLast.MatchString(string(v[len(v)-1])) {
120 return ErrLabelInvalidLastCharacter
121 }
122 // Body characters are a superset of the first character, and we've already
123 // checked that so we can check the entire string here.
124 if !reLabelBody.MatchString(v) {
125 return ErrLabelInvalidCharacter
126 }
127 return nil
128}
129
Serge Bazanski53458ba2024-06-18 09:56:46 +0000130// GetNodeLabel retrieves a node label by key, returning its value or an empty
131// string if no labels with this key is set on the node.
132func GetNodeLabel(labels *cpb.NodeLabels, key string) string {
133 for _, pair := range labels.Pairs {
134 if pair.Key == key {
135 return pair.Value
136 }
137 }
138 return ""
139}
Serge Bazanski6d1ff362024-09-30 15:15:31 +0000140
141// Labels on a node, a map from label key to value.
142type Labels map[string]string
143
144// Equals returns true if these Labels are equal to some others. Equality is
145// defined by having the same set of keys and corresponding values.
146func (l Labels) Equals(others Labels) bool {
147 for k, v := range l {
148 if v2, ok := others[k]; !ok || v != v2 {
149 return false
150 }
151 }
152 for k, v := range others {
153 if v2, ok := l[k]; !ok || v != v2 {
154 return false
155 }
156 }
157 return true
158}
159
160// Filter returns a subset of labels for which pred returns true.
161func (l Labels) Filter(pred func(k, v string) bool) Labels {
162 res := make(Labels)
163 for k, v := range l {
164 if pred(k, v) {
165 res[k] = v
166 }
167 }
168 return res
169}