177 lines
3.9 KiB
Go
177 lines
3.9 KiB
Go
|
|
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
|
||
|
|
}
|