blob: 3e51aaebb5218ee08ac1e7b500c8f06ff2fb6182 [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}