Ctrl K
ComunidadGuías…

internal/portforward/resolver.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package portforward

import (
	"context"
	"fmt"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/client-go/kubernetes"
)

type podResolver struct {
	cs kubernetes.Interface
}

func NewResolver(cs kubernetes.Interface) Resolver {
	return &podResolver{cs: cs}
}

func (p *podResolver) Resolve(ctx context.Context, target Target, port int32) (string, int32, error) {
	if target.Kind == KindPod {
		return target.Name, port, nil
	}
	if target.Kind == KindService {
		return p.resolveService(ctx, target, port)
	}
	return "", 0, fmt.Errorf("cannot port forward to a %s", target.Kind)
}

func (p *podResolver) resolveService(ctx context.Context, target Target, port int32) (string, int32, error) {
	service, err := p.cs.CoreV1().Services(target.Namespace).Get(ctx, target.Name, metav1.GetOptions{})
	if err != nil {
		return "", 0, err
	}
	servicePort, found := portByNumber(service, port)
	if !found {
		return "", 0, fmt.Errorf("service %s/%s does not expose port %d", target.Namespace, target.Name, port)
	}
	if len(service.Spec.Selector) == 0 {
		return "", 0, fmt.Errorf("service %s/%s has no selector", target.Namespace, target.Name)
	}

	pod, err := p.readyPod(ctx, target.Namespace, service.Spec.Selector)
	if err != nil {
		return "", 0, err
	}
	podPort, err := targetPort(pod, servicePort)
	if err != nil {
		return "", 0, err
	}
	return pod.Name, podPort, nil
}

func portByNumber(service *corev1.Service, port int32) (corev1.ServicePort, bool) {
	for _, candidate := range service.Spec.Ports {
		if candidate.Port == port {
			return candidate, true
		}
	}
	return corev1.ServicePort{}, false
}

func (p *podResolver) readyPod(ctx context.Context, namespace string, selector map[string]string) (*corev1.Pod, error) {
	list, err := p.cs.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
		LabelSelector: labels.SelectorFromSet(selector).String(),
	})
	if err != nil {
		return nil, err
	}
	for i := range list.Items {
		pod := &list.Items[i]
		if isReady(pod) {
			return pod, nil
		}
	}
	return nil, fmt.Errorf("no ready pod backs the service in %s", namespace)
}

func isReady(pod *corev1.Pod) bool {
	if pod.Status.Phase != corev1.PodRunning {
		return false
	}
	for _, condition := range pod.Status.Conditions {
		if condition.Type != corev1.PodReady {
			continue
		}
		return condition.Status == corev1.ConditionTrue
	}
	return false
}

const maxPort = 65535

func targetPort(pod *corev1.Pod, servicePort corev1.ServicePort) (int32, error) {
	numeric := servicePort.TargetPort.IntValue()
	if numeric != 0 {
		if numeric < 1 {
			return 0, fmt.Errorf("target port %d is out of range", numeric)
		}
		if numeric > maxPort {
			return 0, fmt.Errorf("target port %d is out of range", numeric)
		}
		return int32(numeric), nil
	}
	name := servicePort.TargetPort.StrVal
	if name == "" {
		return servicePort.Port, nil
	}
	for _, container := range pod.Spec.Containers {
		for _, containerPort := range container.Ports {
			if containerPort.Name == name {
				return containerPort.ContainerPort, nil
			}
		}
	}
	return 0, fmt.Errorf("pod %s has no port named %q", pod.Name, name)
}