Files
flock/pkg/agent/annotations.go
T

177 lines
3.9 KiB
Go
Raw Normal View History

package agent
import (
"fmt"
"net"
"strings"
"code.fritzlab.net/fritzlab/flock/pkg/embed"
)
const annotationPrefix = "flock.fritzlab.net/"
// ParsedAnnotations is the typed view of a Pod's flock annotations.
type ParsedAnnotations struct {
WantV6 bool
WantV4 bool
CIDR6 []*net.IPNet
CIDR4 []*net.IPNet
IPAlgo []embed.Field
Anycast []net.IP
}
// ParseAnnotations applies the design-doc defaults (ipv6=true, ipv4=false)
// and validates the post-merge combination.
func ParseAnnotations(in map[string]string) (*ParsedAnnotations, error) {
out := &ParsedAnnotations{WantV6: true, WantV4: false}
if v, ok := in[annotationPrefix+"ipv6"]; ok {
switch strings.ToLower(strings.TrimSpace(v)) {
case "true":
out.WantV6 = true
case "false":
out.WantV6 = false
default:
return nil, fmt.Errorf("annotation ipv6=%q: must be true or false", v)
}
}
if v, ok := in[annotationPrefix+"ipv4"]; ok {
switch strings.ToLower(strings.TrimSpace(v)) {
case "true":
out.WantV4 = true
case "false":
out.WantV4 = false
default:
return nil, fmt.Errorf("annotation ipv4=%q: must be true or false", v)
}
}
if !out.WantV6 && !out.WantV4 {
return nil, fmt.Errorf("ipv6=false requires ipv4=true (pod must have at least one address)")
}
if v, ok := in[annotationPrefix+"cidr6"]; ok {
nets, err := parseCIDRList(v)
if err != nil {
return nil, fmt.Errorf("annotation cidr6: %w", err)
}
out.CIDR6 = nets
}
if v, ok := in[annotationPrefix+"cidr4"]; ok {
nets, err := parseCIDRList(v)
if err != nil {
return nil, fmt.Errorf("annotation cidr4: %w", err)
}
out.CIDR4 = nets
}
if v, ok := in[annotationPrefix+"ip-algo"]; ok {
fields, err := parseIPAlgo(v)
if err != nil {
return nil, fmt.Errorf("annotation ip-algo: %w", err)
}
out.IPAlgo = fields
}
if v, ok := in[annotationPrefix+"anycast"]; ok {
ips, err := parseIPList(v)
if err != nil {
return nil, fmt.Errorf("annotation anycast: %w", err)
}
out.Anycast = ips
}
return out, nil
}
func parseCIDRList(s string) ([]*net.IPNet, error) {
var out []*net.IPNet
for _, part := range strings.Split(s, ",") {
part = strings.TrimSpace(part)
if part == "" {
continue
}
_, n, err := net.ParseCIDR(part)
if err != nil {
return nil, fmt.Errorf("invalid CIDR %q: %w", part, err)
}
out = append(out, n)
}
if len(out) == 0 {
return nil, fmt.Errorf("empty CIDR list")
}
return out, nil
}
func parseIPList(s string) ([]net.IP, error) {
var out []net.IP
for _, part := range strings.Split(s, ",") {
part = strings.TrimSpace(part)
if part == "" {
continue
}
ip := net.ParseIP(part)
if ip == nil {
return nil, fmt.Errorf("invalid IP %q", part)
}
out = append(out, ip)
}
if len(out) == 0 {
return nil, fmt.Errorf("empty IP list")
}
return out, nil
}
func parseIPAlgo(s string) ([]embed.Field, error) {
var out []embed.Field
for _, part := range strings.Split(s, ",") {
part = strings.TrimSpace(part)
switch part {
case "":
continue
case "namespace":
out = append(out, embed.FieldNamespace)
case "pod":
out = append(out, embed.FieldPod)
case "image":
out = append(out, embed.FieldImage)
default:
return nil, fmt.Errorf("unknown ip-algo field %q (allowed: namespace, pod, image)", part)
}
}
if len(out) == 0 {
return nil, fmt.Errorf("empty ip-algo")
}
return out, nil
}
// CNIArgs parses the K=V;K=V CNI_ARGS string for the kubelet keys we care
// about. Other keys are ignored.
type CNIArgs struct {
PodNamespace string
PodName string
PodUID string
InfraID string
}
func ParseCNIArgs(s string) CNIArgs {
var a CNIArgs
for _, kv := range strings.Split(s, ";") {
eq := strings.IndexByte(kv, '=')
if eq < 0 {
continue
}
k, v := kv[:eq], kv[eq+1:]
switch k {
case "K8S_POD_NAMESPACE":
a.PodNamespace = v
case "K8S_POD_NAME":
a.PodName = v
case "K8S_POD_UID":
a.PodUID = v
case "K8S_POD_INFRA_CONTAINER_ID":
a.InfraID = v
}
}
return a
}