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 }