github.com/Racer159/jackal@v0.32.7-0.20240401174413-0bd2339e4f2e/src/pkg/k8s/pods.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // SPDX-FileCopyrightText: 2021-Present The Jackal Authors
     3  
     4  // Package k8s provides a client for interacting with a Kubernetes cluster.
     5  package k8s
     6  
     7  import (
     8  	"context"
     9  	"sort"
    10  	"time"
    11  
    12  	"github.com/defenseunicorns/pkg/helpers"
    13  	corev1 "k8s.io/api/core/v1"
    14  	"k8s.io/apimachinery/pkg/api/errors"
    15  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    16  )
    17  
    18  const waitLimit = 30
    19  
    20  // GeneratePod creates a new pod without adding it to the k8s cluster.
    21  func (k *K8s) GeneratePod(name, namespace string) *corev1.Pod {
    22  	pod := &corev1.Pod{
    23  		TypeMeta: metav1.TypeMeta{
    24  			APIVersion: corev1.SchemeGroupVersion.String(),
    25  			Kind:       "Pod",
    26  		},
    27  		ObjectMeta: metav1.ObjectMeta{
    28  			Name:      name,
    29  			Namespace: namespace,
    30  		},
    31  	}
    32  
    33  	// Merge in common labels so that later modifications to the pod can't mutate them
    34  	pod.ObjectMeta.Labels = helpers.MergeMap[string](k.Labels, pod.ObjectMeta.Labels)
    35  
    36  	return pod
    37  }
    38  
    39  // DeletePod removes a pod from the cluster by namespace & name.
    40  func (k *K8s) DeletePod(namespace string, name string) error {
    41  	deleteGracePeriod := int64(0)
    42  	deletePolicy := metav1.DeletePropagationForeground
    43  	err := k.Clientset.CoreV1().Pods(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{
    44  		GracePeriodSeconds: &deleteGracePeriod,
    45  		PropagationPolicy:  &deletePolicy,
    46  	})
    47  
    48  	if err != nil {
    49  		return err
    50  	}
    51  
    52  	for {
    53  		// Keep checking for the pod to be deleted
    54  		_, err := k.Clientset.CoreV1().Pods(namespace).Get(context.TODO(), name, metav1.GetOptions{})
    55  		if errors.IsNotFound(err) {
    56  			return nil
    57  		}
    58  		time.Sleep(1 * time.Second)
    59  	}
    60  }
    61  
    62  // DeletePods removes a collection of pods from the cluster by pod lookup.
    63  func (k *K8s) DeletePods(target PodLookup) error {
    64  	deleteGracePeriod := int64(0)
    65  	deletePolicy := metav1.DeletePropagationForeground
    66  	return k.Clientset.CoreV1().Pods(target.Namespace).DeleteCollection(context.TODO(),
    67  		metav1.DeleteOptions{
    68  			GracePeriodSeconds: &deleteGracePeriod,
    69  			PropagationPolicy:  &deletePolicy,
    70  		},
    71  		metav1.ListOptions{
    72  			LabelSelector: target.Selector,
    73  		},
    74  	)
    75  }
    76  
    77  // CreatePod inserts the given pod into the cluster.
    78  func (k *K8s) CreatePod(pod *corev1.Pod) (*corev1.Pod, error) {
    79  	createOptions := metav1.CreateOptions{}
    80  	return k.Clientset.CoreV1().Pods(pod.Namespace).Create(context.TODO(), pod, createOptions)
    81  }
    82  
    83  // GetAllPods returns a list of pods from the cluster for all namespaces.
    84  func (k *K8s) GetAllPods() (*corev1.PodList, error) {
    85  	return k.GetPods(corev1.NamespaceAll)
    86  }
    87  
    88  // GetPods returns a list of pods from the cluster by namespace.
    89  func (k *K8s) GetPods(namespace string) (*corev1.PodList, error) {
    90  	metaOptions := metav1.ListOptions{}
    91  	return k.Clientset.CoreV1().Pods(namespace).List(context.TODO(), metaOptions)
    92  }
    93  
    94  // WaitForPodsAndContainers attempts to find pods matching the given selector and optional inclusion filter
    95  // It will wait up to 90 seconds for the pods to be found and will return a list of matching pod names
    96  // If the timeout is reached, an empty list will be returned.
    97  func (k *K8s) WaitForPodsAndContainers(target PodLookup, include PodFilter) []corev1.Pod {
    98  	for count := 0; count < waitLimit; count++ {
    99  
   100  		pods, err := k.Clientset.CoreV1().Pods(target.Namespace).List(context.TODO(), metav1.ListOptions{
   101  			LabelSelector: target.Selector,
   102  		})
   103  		if err != nil {
   104  			k.Log("Unable to find matching pods: %w", err)
   105  			break
   106  		}
   107  
   108  		k.Log("Found %d pods for target %#v", len(pods.Items), target)
   109  
   110  		var readyPods = []corev1.Pod{}
   111  
   112  		// Sort the pods from newest to oldest
   113  		sort.Slice(pods.Items, func(i, j int) bool {
   114  			return pods.Items[i].CreationTimestamp.After(pods.Items[j].CreationTimestamp.Time)
   115  		})
   116  
   117  		for _, pod := range pods.Items {
   118  			k.Log("Testing pod %q", pod.Name)
   119  
   120  			// If an include function is provided, only keep pods that return true
   121  			if include != nil && !include(pod) {
   122  				continue
   123  			}
   124  
   125  			// Handle container targeting
   126  			if target.Container != "" {
   127  				k.Log("Testing pod %q for container %q", pod.Name, target.Container)
   128  				var matchesInitContainer bool
   129  
   130  				// Check the status of initContainers for a running match
   131  				for _, initContainer := range pod.Status.InitContainerStatuses {
   132  					isRunning := initContainer.State.Running != nil
   133  					if isRunning && initContainer.Name == target.Container {
   134  						// On running match in initContainer break this loop
   135  						matchesInitContainer = true
   136  						readyPods = append(readyPods, pod)
   137  						break
   138  					}
   139  				}
   140  
   141  				// Don't check any further if there's already a match
   142  				if matchesInitContainer {
   143  					continue
   144  				}
   145  
   146  				// Check the status of regular containers for a running match
   147  				for _, container := range pod.Status.ContainerStatuses {
   148  					isRunning := container.State.Running != nil
   149  					if isRunning && container.Name == target.Container {
   150  						readyPods = append(readyPods, pod)
   151  					}
   152  				}
   153  			} else {
   154  				status := pod.Status.Phase
   155  				k.Log("Testing pod %q phase, want (%q) got (%q)", pod.Name, corev1.PodRunning, status)
   156  				// Regular status checking without a container
   157  				if status == corev1.PodRunning {
   158  					readyPods = append(readyPods, pod)
   159  				}
   160  			}
   161  		}
   162  
   163  		if len(readyPods) > 0 {
   164  			return readyPods
   165  		}
   166  
   167  		time.Sleep(3 * time.Second)
   168  	}
   169  
   170  	k.Log("Pod lookup timeout exceeded")
   171  
   172  	return []corev1.Pod{}
   173  }
   174  
   175  // FindPodContainerPort will find a pod's container port from a service and return it.
   176  //
   177  // Returns 0 if no port is found.
   178  func (k *K8s) FindPodContainerPort(svc corev1.Service) int {
   179  	selectorLabelsOfPods := MakeLabels(svc.Spec.Selector)
   180  	pods := k.WaitForPodsAndContainers(PodLookup{
   181  		Namespace: svc.Namespace,
   182  		Selector:  selectorLabelsOfPods,
   183  	}, nil)
   184  
   185  	for _, pod := range pods {
   186  		// Find the matching name on the port in the pod
   187  		for _, container := range pod.Spec.Containers {
   188  			for _, port := range container.Ports {
   189  				if port.Name == svc.Spec.Ports[0].TargetPort.String() {
   190  					return int(port.ContainerPort)
   191  				}
   192  			}
   193  		}
   194  	}
   195  
   196  	return 0
   197  }