blob: f26e5bdb3c4230464f23f4985e8e622046731cae [file] [log] [blame]
Serge Bazanski1f789542024-05-22 14:01:50 +02001package node
2
3import (
4 "fmt"
5 "regexp"
Serge Bazanski53458ba2024-06-18 09:56:46 +00006
7 cpb "source.monogon.dev/metropolis/proto/common"
Serge Bazanski1f789542024-05-22 14:01:50 +02008)
9
10var (
11 reLabelFirstLast = regexp.MustCompile(`^[a-zA-Z0-9]$`)
12 reLabelBody = regexp.MustCompile(`^[a-zA-Z0-9\-._]*$`)
13
14 // ErrLabelEmpty is returned by ValidateLabel if the label key/value is not at
15 // least one character long.
16 ErrLabelEmpty = fmt.Errorf("empty")
17 // ErrLabelTooLong is returned by ValidateLabel if the label key/value is more
18 // than 63 characters long.
19 ErrLabelTooLong = fmt.Errorf("too long")
20 // ErrLabelInvalidFirstCharacter is returned by ValidateLabel if the label
21 // key/value contains an invalid character on the first position.
22 ErrLabelInvalidFirstCharacter = fmt.Errorf("first character not a letter or number")
23 // ErrLabelInvalidLastCharacter is returned by ValidateLabel if the label
24 // key/value contains an invalid character on the last position.
25 ErrLabelInvalidLastCharacter = fmt.Errorf("last character not a letter or number")
26 // ErrLabelInvalidCharacter is returned by ValidateLabel if the label key/value
27 // contains an invalid character.
28 ErrLabelInvalidCharacter = fmt.Errorf("invalid character")
29)
30
31const (
32 // MaxLabelsPerNode is the absolute maximum of labels that can be attached to a
33 // node.
34 MaxLabelsPerNode = 128
35)
36
37// ValidateLabel ensures that a given node label key/value component is valid:
38//
39// 1. 1 to 63 characters long (inclusive);
40// 2. Characters are all ASCII a-z A-Z 0-9 '_', '-' or '.';
41// 3. The first character is ASCII a-z A-Z or 0-9.
42// 4. The last character is ASCII a-z A-Z or 0-9.
43//
44// If it's valid, nil is returned. Otherwise, one of ErrLabelEmpty,
45// ErrLabelTooLong, ErrLabelInvalidFirstCharacter or ErrLabelInvalidCharacter is
46// returned.
47func ValidateLabel(v string) error {
48 if len(v) == 0 {
49 return ErrLabelEmpty
50 }
51 if len(v) > 63 {
52 return ErrLabelTooLong
53 }
54 if !reLabelFirstLast.MatchString(string(v[0])) {
55 return ErrLabelInvalidFirstCharacter
56 }
57 if !reLabelFirstLast.MatchString(string(v[len(v)-1])) {
58 return ErrLabelInvalidLastCharacter
59 }
60 // Body characters are a superset of the first/last characters, and we've already
61 // checked those so we can check the entire string here.
62 if !reLabelBody.MatchString(v) {
63 return ErrLabelInvalidCharacter
64 }
65 return nil
66}
Serge Bazanski53458ba2024-06-18 09:56:46 +000067
68// GetNodeLabel retrieves a node label by key, returning its value or an empty
69// string if no labels with this key is set on the node.
70func GetNodeLabel(labels *cpb.NodeLabels, key string) string {
71 for _, pair := range labels.Pairs {
72 if pair.Key == key {
73 return pair.Value
74 }
75 }
76 return ""
77}
Serge Bazanski6d1ff362024-09-30 15:15:31 +000078
79// Labels on a node, a map from label key to value.
80type Labels map[string]string
81
82// Equals returns true if these Labels are equal to some others. Equality is
83// defined by having the same set of keys and corresponding values.
84func (l Labels) Equals(others Labels) bool {
85 for k, v := range l {
86 if v2, ok := others[k]; !ok || v != v2 {
87 return false
88 }
89 }
90 for k, v := range others {
91 if v2, ok := l[k]; !ok || v != v2 {
92 return false
93 }
94 }
95 return true
96}
97
98// Filter returns a subset of labels for which pred returns true.
99func (l Labels) Filter(pred func(k, v string) bool) Labels {
100 res := make(Labels)
101 for k, v := range l {
102 if pred(k, v) {
103 res[k] = v
104 }
105 }
106 return res
107}