| Serge Bazanski | 1f78954 | 2024-05-22 14:01:50 +0200 | [diff] [blame] | 1 | package node |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "regexp" |
| Serge Bazanski | 53458ba | 2024-06-18 09:56:46 +0000 | [diff] [blame^] | 6 | |
| 7 | cpb "source.monogon.dev/metropolis/proto/common" |
| Serge Bazanski | 1f78954 | 2024-05-22 14:01:50 +0200 | [diff] [blame] | 8 | ) |
| 9 | |
| 10 | var ( |
| 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 | |
| 31 | const ( |
| 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. |
| 47 | func 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 Bazanski | 53458ba | 2024-06-18 09:56:46 +0000 | [diff] [blame^] | 67 | |
| 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. |
| 70 | func 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 | } |