Files
podman/pkg/annotations/validate.go
T
Sakthi Harish faf93bb0ca feat(kube play): add --label flag to specify resource labels
Adds a -l/--label CLI flag to podman kube play and wires it through
the API, bindings, and ABI so user-provided labels are applied to pods,
PVCs, and secrets created from Kubernetes YAML.

Key features:
- Labels are merged with YAML labels (CLI takes precedence)
- Validation uses Kubernetes qualified-name rules (RFC1123 subdomains)
- Value validation enforces 63-char limit and charset rules
- Separate merge and validate functions (SOLID principle)
- Uses maps.Clone for cleaner merging

Fixes:
- Removed incorrect annotations size limit from label validation
- Refactored to call annotations.IsQualifiedName for key validation
- Added proper documentation for max key length enforcement

Closes #26450

Signed-off-by: Sakthi Harish <sakthi.harish@edgeverve.com>
2026-10-06 13:45:32 +00:00

131 lines
4.1 KiB
Go

package annotations
import (
"errors"
"fmt"
"strings"
"go.podman.io/storage/pkg/regexp"
"go.podman.io/podman/v6/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])?"
dns1123SubdomainFmt string = dns1123LabelFmt + "(\\." + dns1123LabelFmt + ")*"
dns1123SubdomainErrorMsg string = "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.Delayed("^" + 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]"
qnameExtCharFmt string = "[-A-Za-z0-9_.]"
qualifiedNameFmt string = "(" + qnameCharFmt + qnameExtCharFmt + "*)?" + qnameCharFmt
qualifiedNameErrMsg string = "must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character"
qualifiedNameMaxLength int = 63
)
var qualifiedNameRegexp = regexp.Delayed("^" + qualifiedNameFmt + "$")
// IsQualifiedName validates keys that follow Kubernetes qualified-name rules
// (used by annotations AND labels): an optional DNS-1123 subdomain prefix and
// '/', followed by a name of up to 63 characters. It returns an error if the
// value does not conform.
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
}