blob: b8293020a4147baaedd8cfb97b52656316829abf [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
Serge Bazanski53458ba2024-06-18 09:56:46 +00008 cpb "source.monogon.dev/metropolis/proto/common"
Serge Bazanski1f789542024-05-22 14:01:50 +02009)
10
11var (
12 reLabelFirstLast = regexp.MustCompile(`^[a-zA-Z0-9]$`)
13 reLabelBody = regexp.MustCompile(`^[a-zA-Z0-9\-._]*$`)
14
15 // ErrLabelEmpty is returned by ValidateLabel if the label key/value is not at
16 // least one character long.
17 ErrLabelEmpty = fmt.Errorf("empty")
18 // ErrLabelTooLong is returned by ValidateLabel if the label key/value is more
19 // than 63 characters long.
20 ErrLabelTooLong = fmt.Errorf("too long")
21 // ErrLabelInvalidFirstCharacter is returned by ValidateLabel if the label
22 // key/value contains an invalid character on the first position.
23 ErrLabelInvalidFirstCharacter = fmt.Errorf("first character not a letter or number")
24 // ErrLabelInvalidLastCharacter is returned by ValidateLabel if the label
25 // key/value contains an invalid character on the last position.
26 ErrLabelInvalidLastCharacter = fmt.Errorf("last character not a letter or number")
27 // ErrLabelInvalidCharacter is returned by ValidateLabel if the label key/value
28 // contains an invalid character.
29 ErrLabelInvalidCharacter = fmt.Errorf("invalid character")
Serge Bazanskidd2b80f2024-09-24 13:06:27 +000030 ErrLabelEmptyPrefix = fmt.Errorf("empty prefix")
31 ErrLabelInvalidPrefix = fmt.Errorf("invalid prefix")
Serge Bazanski1f789542024-05-22 14:01:50 +020032)
33
34const (
35 // MaxLabelsPerNode is the absolute maximum of labels that can be attached to a
36 // node.
37 MaxLabelsPerNode = 128
38)
39
Serge Bazanskidd2b80f2024-09-24 13:06:27 +000040func validatePrefix(prefix string) error {
41 if prefix == "" {
42 return ErrLabelEmptyPrefix
43 }
Jan Schär690c42d2024-11-21 12:10:53 +010044 if err := validateDomainName(prefix); err != nil {
45 return fmt.Errorf("invalid prefix: %w", err)
Serge Bazanskidd2b80f2024-09-24 13:06:27 +000046 }
47 return nil
48}
49
50// ValidateLabelKey ensures that a given node label key is valid:
Serge Bazanski1f789542024-05-22 14:01:50 +020051//
52// 1. 1 to 63 characters long (inclusive);
53// 2. Characters are all ASCII a-z A-Z 0-9 '_', '-' or '.';
54// 3. The first character is ASCII a-z A-Z or 0-9.
55// 4. The last character is ASCII a-z A-Z or 0-9.
Serge Bazanskidd2b80f2024-09-24 13:06:27 +000056// 5. Optional slash-delimited prefix which contains a valid 'DNS subdomain'.
Serge Bazanski1f789542024-05-22 14:01:50 +020057//
58// If it's valid, nil is returned. Otherwise, one of ErrLabelEmpty,
59// ErrLabelTooLong, ErrLabelInvalidFirstCharacter or ErrLabelInvalidCharacter is
60// returned.
Serge Bazanskidd2b80f2024-09-24 13:06:27 +000061func ValidateLabelKey(v string) error {
62 // Split away prefix.
63 parts := strings.Split(v, "/")
64 switch len(parts) {
65 case 1:
66 case 2:
67 prefix := parts[0]
68 if err := validatePrefix(prefix); err != nil {
69 return err
70 }
71 v = parts[1]
72 default:
73 return ErrLabelInvalidPrefix
74 }
75
Serge Bazanski1f789542024-05-22 14:01:50 +020076 if len(v) == 0 {
77 return ErrLabelEmpty
78 }
Serge Bazanskidd2b80f2024-09-24 13:06:27 +000079
Serge Bazanski1f789542024-05-22 14:01:50 +020080 if len(v) > 63 {
81 return ErrLabelTooLong
82 }
83 if !reLabelFirstLast.MatchString(string(v[0])) {
84 return ErrLabelInvalidFirstCharacter
85 }
86 if !reLabelFirstLast.MatchString(string(v[len(v)-1])) {
87 return ErrLabelInvalidLastCharacter
88 }
89 // Body characters are a superset of the first/last characters, and we've already
90 // checked those so we can check the entire string here.
91 if !reLabelBody.MatchString(v) {
92 return ErrLabelInvalidCharacter
93 }
94 return nil
95}
Serge Bazanski53458ba2024-06-18 09:56:46 +000096
Serge Bazanskidd2b80f2024-09-24 13:06:27 +000097// ValidateLabelValue ensures that a given node label value is valid:
98//
99// 1. 0 to 63 characters long (inclusive);
100// 2. Characters are all ASCII a-z A-Z 0-9 '_', '-' or '.';
101// 3. The first character is ASCII a-z A-Z or 0-9.
102// 4. The last character is ASCII a-z A-Z or 0-9.
103//
104// If it's valid, nil is returned. Otherwise, one of ErrLabelTooLong,
105// ErrLabelInvalidFirstCharacter, or ErrLabelInvalidLastCharacter,
106// ErrLabelInvalidCharacter is returned.
107func ValidateLabelValue(v string) error {
108 if len(v) == 0 {
109 return nil
110 }
111 if len(v) > 63 {
112 return ErrLabelTooLong
113 }
114 if !reLabelFirstLast.MatchString(string(v[0])) {
115 return ErrLabelInvalidFirstCharacter
116 }
117 if !reLabelFirstLast.MatchString(string(v[len(v)-1])) {
118 return ErrLabelInvalidLastCharacter
119 }
120 // Body characters are a superset of the first character, and we've already
121 // checked that so we can check the entire string here.
122 if !reLabelBody.MatchString(v) {
123 return ErrLabelInvalidCharacter
124 }
125 return nil
126}
127
Serge Bazanski53458ba2024-06-18 09:56:46 +0000128// GetNodeLabel retrieves a node label by key, returning its value or an empty
129// string if no labels with this key is set on the node.
130func GetNodeLabel(labels *cpb.NodeLabels, key string) string {
131 for _, pair := range labels.Pairs {
132 if pair.Key == key {
133 return pair.Value
134 }
135 }
136 return ""
137}
Serge Bazanski6d1ff362024-09-30 15:15:31 +0000138
139// Labels on a node, a map from label key to value.
140type Labels map[string]string
141
142// Equals returns true if these Labels are equal to some others. Equality is
143// defined by having the same set of keys and corresponding values.
144func (l Labels) Equals(others Labels) bool {
145 for k, v := range l {
146 if v2, ok := others[k]; !ok || v != v2 {
147 return false
148 }
149 }
150 for k, v := range others {
151 if v2, ok := l[k]; !ok || v != v2 {
152 return false
153 }
154 }
155 return true
156}
157
158// Filter returns a subset of labels for which pred returns true.
159func (l Labels) Filter(pred func(k, v string) bool) Labels {
160 res := make(Labels)
161 for k, v := range l {
162 if pred(k, v) {
163 res[k] = v
164 }
165 }
166 return res
167}