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 }