func (c converter) JoinProfileRevisions(nsRev, saRev string) string {
	return nsRev + "/" + saRev
}
JoinProfileRevisions constructs the revision from the individual namespace and serviceaccount
revisions.
This is conditional on the feature flag for serviceaccount set or not.
package conversion
import (
	"fmt"
	"sort"
	"strings"
	log "github.com/sirupsen/logrus"
	kapiv1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	discovery "k8s.io/api/discovery/v1beta1"
	networkingv1 "k8s.io/api/networking/v1"
	"k8s.io/apimachinery/pkg/util/intstr"
	apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3"
	"github.com/projectcalico/api/pkg/lib/numorstring"
	"github.com/projectcalico/libcalico-go/lib/backend/model"
	"github.com/projectcalico/libcalico-go/lib/names"
	cnet "github.com/projectcalico/libcalico-go/lib/net"
)
var (
	protoTCP = kapiv1.ProtocolTCP
)
type selectorType int8
const (
	SelectorNamespace selectorType = iota
	SelectorPod
)
type Converter interface {
	WorkloadEndpointConverter
	ParseWorkloadEndpointName(workloadName string) (names.WorkloadEndpointIdentifiers, error)
	NamespaceToProfile(ns *kapiv1.Namespace) (*model.KVPair, error)
	IsValidCalicoWorkloadEndpoint(pod *kapiv1.Pod) bool
	IsReadyCalicoPod(pod *kapiv1.Pod) bool
	IsScheduled(pod *kapiv1.Pod) bool
	IsHostNetworked(pod *kapiv1.Pod) bool
	HasIPAddress(pod *kapiv1.Pod) bool
	StagedKubernetesNetworkPolicyToStagedName(stagedK8sName string) string
	K8sNetworkPolicyToCalico(np *networkingv1.NetworkPolicy) (*model.KVPair, error)
	EndpointSliceToKVP(svc *discovery.EndpointSlice) (*model.KVPair, error)
	ProfileNameToNamespace(profileName string) (string, error)
	ServiceAccountToProfile(sa *kapiv1.ServiceAccount) (*model.KVPair, error)
	ProfileNameToServiceAccount(profileName string) (ns, sa string, err error)
	JoinProfileRevisions(nsRev, saRev string) string
	SplitProfileRevision(rev string) (nsRev string, saRev string, err error)
}
type converter struct {
	WorkloadEndpointConverter
}
func NewConverter() Converter {
	return &converter{
		WorkloadEndpointConverter: NewWorkloadEndpointConverter(),
	}
}
func (c converter) ParseWorkloadEndpointName(workloadName string) (names.WorkloadEndpointIdentifiers, error) {
	return names.ParseWorkloadEndpointName(workloadName)
}
func (c converter) NamespaceToProfile(ns *kapiv1.Namespace) (*model.KVPair, error) {
	labels := map[string]string{}
	for k, v := range ns.Labels {
		labels[NamespaceLabelPrefix+k] = v
	}
	labels[NamespaceLabelPrefix+NameLabel] = ns.Name
	name := NamespaceProfileNamePrefix + ns.Name
	profile := apiv3.NewProfile()
	profile.ObjectMeta = metav1.ObjectMeta{
		Name:              name,
		CreationTimestamp: ns.CreationTimestamp,
		UID:               ns.UID,
	}
	profile.Spec = apiv3.ProfileSpec{
		Ingress:       []apiv3.Rule{{Action: apiv3.Allow}},
		Egress:        []apiv3.Rule{{Action: apiv3.Allow}},
		LabelsToApply: labels,
	}
	kvp := model.KVPair{
		Key: model.ResourceKey{
			Name: name,
			Kind: apiv3.KindProfile,
		},
		Value:    profile,
		Revision: c.JoinProfileRevisions(ns.ResourceVersion, ""),
	}
	return &kvp, nil
}
func (c converter) IsValidCalicoWorkloadEndpoint(pod *kapiv1.Pod) bool {
	if c.IsHostNetworked(pod) {
		log.WithField("pod", pod.Name).Debug("Pod is host networked.")
		return false
	} else if !c.IsScheduled(pod) {
		log.WithField("pod", pod.Name).Debug("Pod is not scheduled.")
		return false
	}
	return true
}
func (c converter) IsReadyCalicoPod(pod *kapiv1.Pod) bool {
	if !c.IsValidCalicoWorkloadEndpoint(pod) {
		return false
	} else if !c.HasIPAddress(pod) {
		log.WithField("pod", pod.Name).Debug("Pod does not have an IP address.")
		return false
	}
	return true
}
const (
	podCompleted kapiv1.PodPhase = "Completed"
)
func IsFinished(pod *kapiv1.Pod) bool {
	if pod.DeletionTimestamp != nil {
		if ip, ok := pod.Annotations[AnnotationPodIP]; ok && ip == "" {
			log.Debug("Pod is being deleted and IPs have been removed by Calico CNI.")
			return true
		} else if ips, ok := pod.Annotations[AnnotationAWSPodIPs]; ok && ips == "" {
			log.Debug("Pod is being deleted and IPs have been removed by AWS CNI.")
			return true
		}
	}
	switch pod.Status.Phase {
	case kapiv1.PodFailed, kapiv1.PodSucceeded, podCompleted:
		log.Debug("Pod phase is failed/succeeded/completed.")
		return true
	}
	return false
}
func (c converter) IsScheduled(pod *kapiv1.Pod) bool {
	return pod.Spec.NodeName != ""
}
func (c converter) IsHostNetworked(pod *kapiv1.Pod) bool {
	return pod.Spec.HostNetwork
}
func (c converter) HasIPAddress(pod *kapiv1.Pod) bool {
	return pod.Status.PodIP != "" || pod.Annotations[AnnotationPodIP] != "" || pod.Annotations[AnnotationAWSPodIPs] != ""
}
func getPodIPs(pod *kapiv1.Pod) ([]*cnet.IPNet, error) {
	var podIPs []string
	if ips := pod.Status.PodIPs; len(ips) != 0 {
		log.WithField("ips", ips).Debug("PodIPs field filled in")
		for _, ip := range ips {
			podIPs = append(podIPs, ip.IP)
		}
	} else if ip := pod.Status.PodIP; ip != "" {
		log.WithField("ip", ip).Debug("PodIP field filled in")
		podIPs = append(podIPs, ip)
	} else if ips := pod.Annotations[AnnotationPodIPs]; ips != "" {
		log.WithField("ips", ips).Debug("No PodStatus IPs, use Calico plural annotation")
		podIPs = append(podIPs, strings.Split(ips, ",")...)
	} else if ip := pod.Annotations[AnnotationPodIP]; ip != "" {
		log.WithField("ip", ip).Debug("No PodStatus IPs, use Calico singular annotation")
		podIPs = append(podIPs, ip)
	} else if ips := pod.Annotations[AnnotationAWSPodIPs]; ips != "" {
		log.WithField("ips", ips).Debug("No PodStatus IPs, use AWS VPC annotation")
		podIPs = append(podIPs, strings.Split(ips, ",")...)
	} else {
		log.Debug("Pod has no IP")
		return nil, nil
	}
	var podIPNets []*cnet.IPNet
	for _, ip := range podIPs {
		_, ipNet, err := cnet.ParseCIDROrIP(ip)
		if err != nil {
			log.WithFields(log.Fields{"ip": ip, "pod": pod.Name}).WithError(err).Error("Failed to parse pod IP")
			return nil, err
		}
		podIPNets = append(podIPNets, ipNet)
	}
	return podIPNets, nil
}
func (c converter) StagedKubernetesNetworkPolicyToStagedName(stagedK8sName string) string {
	return fmt.Sprintf(K8sNetworkPolicyNamePrefix + stagedK8sName)
}
func (c converter) EndpointSliceToKVP(slice *discovery.EndpointSlice) (*model.KVPair, error) {
	return &model.KVPair{
		Key: model.ResourceKey{
			Name:      slice.Name,
			Namespace: slice.Namespace,
			Kind:      model.KindKubernetesEndpointSlice,
		},
		Value:    slice.DeepCopy(),
		Revision: slice.ResourceVersion,
	}, nil
}
func (c converter) K8sNetworkPolicyToCalico(np *networkingv1.NetworkPolicy) (*model.KVPair, error) {
	policyName := fmt.Sprintf(K8sNetworkPolicyNamePrefix + np.Name)
	order := float64(1000.0)
	var ingressRules []apiv3.Rule
	for _, r := range np.Spec.Ingress {
		rules, err := c.k8sRuleToCalico(r.From, r.Ports, np.Namespace, true)
		if err != nil {
			log.WithError(err).Warn("dropping k8s rule that couldn't be converted.")
		} else {
			ingressRules = append(ingressRules, rules...)
		}
	}
	var egressRules []apiv3.Rule
	for _, r := range np.Spec.Egress {
		rules, err := c.k8sRuleToCalico(r.To, r.Ports, np.Namespace, false)
		if err != nil {
			log.WithError(err).Warn("dropping k8s rule that couldn't be converted")
		} else {
			egressRules = append(egressRules, rules...)
		}
	}
	ingress := false
	egress := false
	for _, policyType := range np.Spec.PolicyTypes {
		switch policyType {
		case networkingv1.PolicyTypeIngress:
			ingress = true
		case networkingv1.PolicyTypeEgress:
			egress = true
		}
	}
	types := []apiv3.PolicyType{}
	if ingress {
		types = append(types, apiv3.PolicyTypeIngress)
	}
	if egress {
		types = append(types, apiv3.PolicyTypeEgress)
	} else if len(egressRules) > 0 {
		log.Warn("K8s PolicyTypes don't include 'egress', but NetworkPolicy has egress rules.")
	}
	if len(types) == 0 {
		types = append(types, apiv3.PolicyTypeIngress)
	}
	policy := apiv3.NewNetworkPolicy()
	policy.ObjectMeta = metav1.ObjectMeta{
		Name:              policyName,
		Namespace:         np.Namespace,
		CreationTimestamp: np.CreationTimestamp,
		UID:               np.UID,
		ResourceVersion:   np.ResourceVersion,
	}
	policy.Spec = apiv3.NetworkPolicySpec{
		Order:    &order,
		Selector: c.k8sSelectorToCalico(&np.Spec.PodSelector, SelectorPod),
		Ingress:  ingressRules,
		Egress:   egressRules,
		Types:    types,
	}
	return &model.KVPair{
		Key: model.ResourceKey{
			Name:      policyName,
			Namespace: np.Namespace,
			Kind:      apiv3.KindNetworkPolicy,
		},
		Value:    policy,
		Revision: np.ResourceVersion,
	}, nil
}
func (c converter) k8sSelectorToCalico(s *metav1.LabelSelector, selectorType selectorType) string {
	selectors := []string{}
	if selectorType == SelectorPod {
		selectors = append(selectors, fmt.Sprintf("%s == 'k8s'", apiv3.LabelOrchestrator))
	}
	if s == nil {
		return strings.Join(selectors, " && ")
	}
	if selectorType == SelectorNamespace && len(s.MatchLabels) == 0 && len(s.MatchExpressions) == 0 {
		return "all()"
	}
	keys := make([]string, 0, len(s.MatchLabels))
	for k := range s.MatchLabels {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	for _, k := range keys {
		v := s.MatchLabels[k]
		selectors = append(selectors, fmt.Sprintf("%s == '%s'", k, v))
	}
	for _, e := range s.MatchExpressions {
		valueList := strings.Join(e.Values, "', '")
		switch e.Operator {
		case metav1.LabelSelectorOpIn:
			selectors = append(selectors, fmt.Sprintf("%s in { '%s' }", e.Key, valueList))
		case metav1.LabelSelectorOpNotIn:
			selectors = append(selectors, fmt.Sprintf("%s not in { '%s' }", e.Key, valueList))
		case metav1.LabelSelectorOpExists:
			selectors = append(selectors, fmt.Sprintf("has(%s)", e.Key))
		case metav1.LabelSelectorOpDoesNotExist:
			selectors = append(selectors, fmt.Sprintf("! has(%s)", e.Key))
		}
	}
	return strings.Join(selectors, " && ")
}
func (c converter) k8sRuleToCalico(rPeers []networkingv1.NetworkPolicyPeer, rPorts []networkingv1.NetworkPolicyPort, ns string, ingress bool) ([]apiv3.Rule, error) {
	rules := []apiv3.Rule{}
	peers := []*networkingv1.NetworkPolicyPeer{}
	ports := []*networkingv1.NetworkPolicyPort{}
	for _, f := range rPeers {
		peers = append(peers, &networkingv1.NetworkPolicyPeer{
			NamespaceSelector: f.NamespaceSelector,
			PodSelector:       f.PodSelector,
			IPBlock:           f.IPBlock,
		})
	}
	for _, p := range rPorts {
		port := networkingv1.NetworkPolicyPort{}
		if p.Port != nil {
			portval := intstr.FromString(p.Port.String())
			port.Port = &portval
		}
		if p.Protocol != nil {
			protval := kapiv1.Protocol(fmt.Sprintf("%s", *p.Protocol))
			port.Protocol = &protval
		} else {
			port.Protocol = &protoTCP
		}
		if p.EndPort != nil {
			port.EndPort = p.EndPort
		}
		ports = append(ports, &port)
	}
	if len(peers) == 0 {
		peers = []*networkingv1.NetworkPolicyPeer{nil}
	}
	if len(ports) == 0 {
		ports = []*networkingv1.NetworkPolicyPort{nil}
	}
	protocolPorts := map[string][]numorstring.Port{}
	for _, port := range ports {
		protocol, calicoPorts, err := c.k8sPortToCalicoFields(port)
		if err != nil {
			return nil, fmt.Errorf("failed to parse k8s port: %s", err)
		}
		if protocol == nil && calicoPorts == nil {
			protocolPorts = map[string][]numorstring.Port{"": nil}
			break
		}
		pStr := protocol.String()
		if calicoPorts == nil {
			protocolPorts[pStr] = nil
		} else if _, ok := protocolPorts[pStr]; !ok || len(protocolPorts[pStr]) > 0 {
			protocolPorts[pStr] = append(protocolPorts[pStr], calicoPorts...)
		}
	}
	protocols := make([]string, 0, len(protocolPorts))
	for k := range protocolPorts {
		protocols = append(protocols, k)
	}
	sort.Strings(protocols)
	for _, protocolStr := range protocols {
		calicoPorts := protocolPorts[protocolStr]
		calicoPorts = SimplifyPorts(calicoPorts)
		var protocol *numorstring.Protocol
		if protocolStr != "" {
			p := numorstring.ProtocolFromString(protocolStr)
			protocol = &p
		}
		for _, peer := range peers {
			selector, nsSelector, nets, notNets := c.k8sPeerToCalicoFields(peer, ns)
			if ingress {
				rules = append(rules, apiv3.Rule{
					Action:   "Allow",
					Protocol: protocol,
					Source: apiv3.EntityRule{
						Selector:          selector,
						NamespaceSelector: nsSelector,
						Nets:              nets,
						NotNets:           notNets,
					},
					Destination: apiv3.EntityRule{
						Ports: calicoPorts,
					},
				})
			} else {
				rules = append(rules, apiv3.Rule{
					Action:   "Allow",
					Protocol: protocol,
					Destination: apiv3.EntityRule{
						Ports:             calicoPorts,
						Selector:          selector,
						NamespaceSelector: nsSelector,
						Nets:              nets,
						NotNets:           notNets,
					},
				})
			}
		}
	}
	return rules, nil
}
func SimplifyPorts(ports []numorstring.Port) []numorstring.Port {
	if len(ports) <= 1 {
		return ports
	}
	var numericPorts []int
	var outputPorts []numorstring.Port
	for _, p := range ports {
		if p.PortName != "" {
			outputPorts = append(outputPorts, p)
		} else {
			for i := int(p.MinPort); i <= int(p.MaxPort); i++ {
				numericPorts = append(numericPorts, i)
			}
		}
	}
	if len(numericPorts) <= 1 {
		return ports
	}
	sort.Ints(numericPorts)
	for len(numericPorts) > 0 {
		firstPortInRange := numericPorts[0]
		lastPortInRange := firstPortInRange
		numericPorts = numericPorts[1:]
		for len(numericPorts) > 0 {
			nextPort := numericPorts[0]
			if nextPort > lastPortInRange+1 {
				break
			}
			lastPortInRange = nextPort
			numericPorts = numericPorts[1:]
		}
		outputPorts = appendPortRange(outputPorts, firstPortInRange, lastPortInRange)
	}
	return outputPorts
}
func appendPortRange(ports []numorstring.Port, first, last int) []numorstring.Port {
	portRange, err := numorstring.PortFromRange(uint16(first), uint16(last))
	if err != nil {
		log.WithError(err).Panic("Failed to make port range from ports that should have been pre-validated.")
	}
	return append(ports, portRange)
}
func (c converter) k8sPortToCalicoFields(port *networkingv1.NetworkPolicyPort) (protocol *numorstring.Protocol, dstPorts []numorstring.Port, err error) {
	if port == nil {
		return
	}
	dstPorts, err = c.k8sPortToCalico(*port)
	if err != nil {
		return
	}
	protocol = c.k8sProtocolToCalico(port.Protocol)
	return
}
func (c converter) k8sProtocolToCalico(protocol *kapiv1.Protocol) *numorstring.Protocol {
	if protocol != nil {
		p := numorstring.ProtocolFromString(string(*protocol))
		return &p
	}
	return nil
}
func (c converter) k8sPeerToCalicoFields(peer *networkingv1.NetworkPolicyPeer, ns string) (selector, nsSelector string, nets []string, notNets []string) {
	if peer == nil {
		return
	}
	if peer.IPBlock != nil {
		_, ipNet, err := cnet.ParseCIDR(peer.IPBlock.CIDR)
		if err != nil {
			log.WithField("cidr", peer.IPBlock.CIDR).WithError(err).Error("Failed to parse CIDR")
			return
		}
		nets = []string{ipNet.String()}
		for _, exception := range peer.IPBlock.Except {
			_, ipNet, err = cnet.ParseCIDR(exception)
			if err != nil {
				log.WithField("cidr", exception).WithError(err).Error("Failed to parse CIDR")
				return
			}
			notNets = append(notNets, ipNet.String())
		}
		return
	}
	selector = c.k8sSelectorToCalico(peer.PodSelector, SelectorPod)
	nsSelector = c.k8sSelectorToCalico(peer.NamespaceSelector, SelectorNamespace)
	return
}
func (c converter) k8sPortToCalico(port networkingv1.NetworkPolicyPort) ([]numorstring.Port, error) {
	var portList []numorstring.Port
	if port.Port != nil {
		calicoPort := port.Port.String()
		if port.EndPort != nil {
			calicoPort = fmt.Sprintf("%s:%d", calicoPort, *port.EndPort)
		}
		p, err := numorstring.PortFromString(calicoPort)
		if err != nil {
			return nil, fmt.Errorf("invalid port %+v: %s", calicoPort, err)
		}
		return append(portList, p), nil
	}
	return portList, nil
}
func (c converter) ProfileNameToNamespace(profileName string) (string, error) {
	if !strings.HasPrefix(profileName, NamespaceProfileNamePrefix) {
		return "", fmt.Errorf("Profile %s not backed by a Namespace", profileName)
	}
	return strings.TrimPrefix(profileName, NamespaceProfileNamePrefix), nil
}
func serviceAccountNameToProfileName(sa, namespace string) string {
	if namespace == "" {
		namespace = "default"
	}
	return ServiceAccountProfileNamePrefix + namespace + "." + sa
}
func (c converter) ServiceAccountToProfile(sa *kapiv1.ServiceAccount) (*model.KVPair, error) {
	labels := map[string]string{}
	for k, v := range sa.ObjectMeta.Labels {
		labels[ServiceAccountLabelPrefix+k] = v
	}
	labels[ServiceAccountLabelPrefix+NameLabel] = sa.Name
	name := serviceAccountNameToProfileName(sa.Name, sa.Namespace)
	profile := apiv3.NewProfile()
	profile.ObjectMeta = metav1.ObjectMeta{
		Name:              name,
		CreationTimestamp: sa.CreationTimestamp,
		UID:               sa.UID,
	}
	profile.Spec.LabelsToApply = labels
	kvp := model.KVPair{
		Key: model.ResourceKey{
			Name: name,
			Kind: apiv3.KindProfile,
		},
		Value:    profile,
		Revision: c.JoinProfileRevisions("", sa.ResourceVersion),
	}
	return &kvp, nil
}
func (c converter) ProfileNameToServiceAccount(profileName string) (ns, sa string, err error) {
	if !strings.HasPrefix(profileName, ServiceAccountProfileNamePrefix) {
		err = fmt.Errorf("Profile %s not backed by a ServiceAccount", profileName)
		return
	}
	names := strings.SplitN(profileName, ".", 3)
	if len(names) != 3 {
		err = fmt.Errorf("Profile %s is not formatted correctly", profileName)
		return
	}
	ns = names[1]
	sa = names[2]
	return
}