Ctrl K
ComunidadGuías…

pkg/k8score/exec.go

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

Archivo original ↗Preguntar a KiBot
package k8score

import (
	"fmt"
	"slices"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/remotecommand"

	rcpkg "github.com/skyhook-io/radar/pkg/remotecommand"
)

// NewPodExecExecutor creates an executor for running commands in a pod container.
// Tries WebSocket first (k8s ≥1.29); falls back to SPDY on upgrade failure for older clusters.
// The caller uses the returned Executor to call StreamWithContext.
func NewPodExecExecutor(client kubernetes.Interface, config *rest.Config, namespace, podName, containerName string, command []string, tty bool) (remotecommand.Executor, error) {
	return newPodExecutor(client, config, namespace, podName, &corev1.PodExecOptions{
		Container: containerName,
		Command:   command,
		Stdin:     true,
		Stdout:    true,
		// When TTY is true, the terminal muxes stderr into stdout, so Stderr must be false.
		// Setting both TTY and Stderr causes stream errors on some API servers; matches kubectl exec -it.
		Stderr: !tty,
		TTY:    tty,
	})
}

// NewPodCommandExecutor creates an executor for a non-interactive command:
// no stdin and no TTY, with stdout and stderr kept apart, like
// `kubectl exec pod -- cmd`. Without stdin the command sees EOF at once
// instead of waiting on a stream nobody writes to.
func NewPodCommandExecutor(client kubernetes.Interface, config *rest.Config, namespace, podName, containerName string, command []string) (remotecommand.Executor, error) {
	return newPodExecutor(client, config, namespace, podName, &corev1.PodExecOptions{
		Container: containerName,
		Command:   command,
		Stdout:    true,
		Stderr:    true,
	})
}

func newPodExecutor(client kubernetes.Interface, config *rest.Config, namespace, podName string, opts *corev1.PodExecOptions) (remotecommand.Executor, error) {
	if client == nil {
		return nil, fmt.Errorf("kubernetes client not initialized")
	}
	req := client.CoreV1().RESTClient().Post().
		Resource("pods").
		Name(podName).
		Namespace(namespace).
		SubResource("exec").
		VersionedParams(opts, scheme.ParameterCodec)

	executor, err := rcpkg.NewExecutor(config, req.URL())
	if err != nil {
		return nil, fmt.Errorf("failed to create exec executor for %s/%s/%s: %w", namespace, podName, opts.Container, err)
	}

	return executor, nil
}

// DefaultContainerAnnotation is the client-side convention kubectl, k9s, and
// Lens use to mark the "main" container of a multi-container pod. Service
// meshes (Istio, Linkerd, ASM) set it during injection to point past their
// sidecar. Clients that simply default to containers[0] can land in a
// distroless mesh proxy with no shell.
const DefaultContainerAnnotation = "kubectl.kubernetes.io/default-container"

// DefaultExecContainer picks the container to target when the request doesn't
// name one. It honors DefaultContainerAnnotation (matching kubectl exec
// behavior). Falls back to the first non-mesh app container; returns "" when none is available.
func DefaultExecContainer(pod *corev1.Pod) string {
	if name := pod.Annotations[DefaultContainerAnnotation]; name != "" && slices.Contains(ExecContainerNames(pod), name) {
		return name
	}

	for _, c := range pod.Spec.Containers {
		if MeshProxyContainers[c.Name] == "" {
			return c.Name
		}
	}
	return ""
}

// MeshProxyContainers identifies injected infrastructure proxies.
var MeshProxyContainers = map[string]string{
	"istio-proxy":      "Istio",
	"linkerd-proxy":    "Linkerd",
	"consul-dataplane": "Consul",
}

// ExecContainerNames includes regular, init and ephemeral containers.
// The API server enforces whether a selected container is running.
func ExecContainerNames(pod *corev1.Pod) []string {
	names := make([]string, 0, len(pod.Spec.Containers)+len(pod.Spec.InitContainers)+len(pod.Spec.EphemeralContainers))
	for _, c := range pod.Spec.Containers {
		names = append(names, c.Name)
	}
	for _, c := range pod.Spec.InitContainers {
		names = append(names, c.Name)
	}
	for _, c := range pod.Spec.EphemeralContainers {
		names = append(names, c.Name)
	}
	return names
}