Files
podman/pkg/annotations/validate.go
Paul Holzinger c17daf2b09 update golangci-lint to 1.60.1
Fixes new spotted issues around printf() formats and using os.Setenv()
in tests.

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
2024-08-19 11:41:28 +02:00

126 lines
4.1 KiB
Go

package annotations
import (
"errors"
"fmt"
"regexp"
"strings"
"github.com/containers/podman/v5/libpod/define"
)
// regexErrorMsg returns a string explanation of a regex validation failure.
func regexErrorMsg(msg string, fmt string, examples ...string) string {
if len(examples) == 0 {
return msg + " (regex used for validation is '" + fmt + "')"
}
msg += " (e.g. "
for i := range examples {
if i > 0 {
msg += " or "
}
msg += "'" + examples[i] + "', "
}
msg += "regex used for validation is '" + fmt + "')"
return msg
}
const dns1123LabelFmt string = "[a-z0-9]([-a-z0-9]*[a-z0-9])?"
const dns1123SubdomainFmt string = dns1123LabelFmt + "(\\." + dns1123LabelFmt + ")*"
const dns1123SubdomainErrorMsg string = "annotations must be formatted as a valid lowercase RFC1123 subdomain of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character"
// DNS1123SubdomainMaxLength is a subdomain's max length in DNS (RFC 1123)
const DNS1123SubdomainMaxLength int = 253
var dns1123SubdomainRegexp = regexp.MustCompile("^" + dns1123SubdomainFmt + "$")
// isDNS1123Subdomain tests for a string that conforms to the definition of a
// subdomain in DNS (RFC 1123).
func isDNS1123Subdomain(value string) error {
if len(value) > DNS1123SubdomainMaxLength {
return fmt.Errorf("prefix part must be no more than %d characters", DNS1123SubdomainMaxLength)
}
if !dns1123SubdomainRegexp.MatchString(value) {
return errors.New(regexErrorMsg(dns1123SubdomainErrorMsg, dns1123SubdomainFmt, "example.com"))
}
return nil
}
const qnameCharFmt string = "[A-Za-z0-9]"
const qnameExtCharFmt string = "[-A-Za-z0-9_.]"
const qualifiedNameFmt string = "(" + qnameCharFmt + qnameExtCharFmt + "*)?" + qnameCharFmt
const qualifiedNameErrMsg string = "must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character"
const qualifiedNameMaxLength int = 63
var qualifiedNameRegexp = regexp.MustCompile("^" + qualifiedNameFmt + "$")
// isQualifiedName tests whether the value passed is what Kubernetes calls a
// "qualified name". This is a format used in various places throughout the
// system. If the value is not valid, a list of error strings is returned.
// Otherwise an empty list (or nil) is returned.
func isQualifiedName(value string) error {
parts := strings.Split(value, "/")
var name string
switch len(parts) {
case 1:
name = parts[0]
case 2:
var prefix string
prefix, name = parts[0], parts[1]
if len(prefix) == 0 {
return fmt.Errorf("prefix part of %s must be non-empty", value)
} else if err := isDNS1123Subdomain(prefix); err != nil {
return err
}
default:
return fmt.Errorf("a qualified name of %s "+
regexErrorMsg(qualifiedNameErrMsg, qualifiedNameFmt, "MyName", "my.name", "123-abc")+
" with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')", value)
}
if len(name) == 0 {
return fmt.Errorf("name part of %s must be non-empty", value)
} else if len(name) > qualifiedNameMaxLength {
return fmt.Errorf("name part of %s must be no more than %d characters", value, qualifiedNameMaxLength)
}
if !qualifiedNameRegexp.MatchString(name) {
return fmt.Errorf("name part of %s "+
regexErrorMsg(qualifiedNameErrMsg, qualifiedNameFmt, "MyName", "my.name", "123-abc"), value)
}
return nil
}
func validateAnnotationsSize(annotations map[string]string) error {
var totalSize int64
for k, v := range annotations {
totalSize += (int64)(len(k)) + (int64)(len(v))
}
if totalSize > (int64)(define.TotalAnnotationSizeLimitB) {
return fmt.Errorf("annotations size %d is larger than limit %d", totalSize, define.TotalAnnotationSizeLimitB)
}
return nil
}
// ValidateAnnotations validates that a set of annotations are correctly
// defined.
func ValidateAnnotations(annotations map[string]string) error {
for k := range annotations {
// The rule is QualifiedName except that case doesn't matter,
// so convert to lowercase before checking.
if err := isQualifiedName(strings.ToLower(k)); err != nil {
return err
}
}
if err := validateAnnotationsSize(annotations); err != nil {
return err
}
return nil
}