blob: 3e51aaebb5218ee08ac1e7b500c8f06ff2fb6182 [file] [log] [blame]
package node
import (
"fmt"
"regexp"
cpb "source.monogon.dev/metropolis/proto/common"
)
var (
reLabelFirstLast = regexp.MustCompile(`^[a-zA-Z0-9]$`)
reLabelBody = regexp.MustCompile(`^[a-zA-Z0-9\-._]*$`)
// ErrLabelEmpty is returned by ValidateLabel if the label key/value is not at
// least one character long.
ErrLabelEmpty = fmt.Errorf("empty")
// ErrLabelTooLong is returned by ValidateLabel if the label key/value is more
// than 63 characters long.
ErrLabelTooLong = fmt.Errorf("too long")
// ErrLabelInvalidFirstCharacter is returned by ValidateLabel if the label
// key/value contains an invalid character on the first position.
ErrLabelInvalidFirstCharacter = fmt.Errorf("first character not a letter or number")
// ErrLabelInvalidLastCharacter is returned by ValidateLabel if the label
// key/value contains an invalid character on the last position.
ErrLabelInvalidLastCharacter = fmt.Errorf("last character not a letter or number")
// ErrLabelInvalidCharacter is returned by ValidateLabel if the label key/value
// contains an invalid character.
ErrLabelInvalidCharacter = fmt.Errorf("invalid character")
)
const (
// MaxLabelsPerNode is the absolute maximum of labels that can be attached to a
// node.
MaxLabelsPerNode = 128
)
// ValidateLabel ensures that a given node label key/value component is valid:
//
// 1. 1 to 63 characters long (inclusive);
// 2. Characters are all ASCII a-z A-Z 0-9 '_', '-' or '.';
// 3. The first character is ASCII a-z A-Z or 0-9.
// 4. The last character is ASCII a-z A-Z or 0-9.
//
// If it's valid, nil is returned. Otherwise, one of ErrLabelEmpty,
// ErrLabelTooLong, ErrLabelInvalidFirstCharacter or ErrLabelInvalidCharacter is
// returned.
func ValidateLabel(v string) error {
if len(v) == 0 {
return ErrLabelEmpty
}
if len(v) > 63 {
return ErrLabelTooLong
}
if !reLabelFirstLast.MatchString(string(v[0])) {
return ErrLabelInvalidFirstCharacter
}
if !reLabelFirstLast.MatchString(string(v[len(v)-1])) {
return ErrLabelInvalidLastCharacter
}
// Body characters are a superset of the first/last characters, and we've already
// checked those so we can check the entire string here.
if !reLabelBody.MatchString(v) {
return ErrLabelInvalidCharacter
}
return nil
}
// GetNodeLabel retrieves a node label by key, returning its value or an empty
// string if no labels with this key is set on the node.
func GetNodeLabel(labels *cpb.NodeLabels, key string) string {
for _, pair := range labels.Pairs {
if pair.Key == key {
return pair.Value
}
}
return ""
}