github.com/kubeshop/testkube@v1.17.23/pkg/executor/containerexecutor/logs.go (about) 1 package containerexecutor 2 3 import ( 4 "bufio" 5 "context" 6 "fmt" 7 "io" 8 "sync" 9 10 corev1 "k8s.io/api/core/v1" 11 "k8s.io/apimachinery/pkg/util/wait" 12 13 "github.com/kubeshop/testkube/pkg/executor" 14 "github.com/kubeshop/testkube/pkg/utils" 15 ) 16 17 // TailJobLogs - locates logs for job pod(s) 18 // These methods here are similar to Job executor, but they don't require the json structure. 19 func (c *ContainerExecutor) TailJobLogs(ctx context.Context, id, namespace string, logs chan []byte) (err error) { 20 podsClient := c.clientSet.CoreV1().Pods(namespace) 21 pods, err := executor.GetJobPods(ctx, podsClient, id, 1, 10) 22 if err != nil { 23 close(logs) 24 return err 25 } 26 27 for _, pod := range pods.Items { 28 if pod.Labels["job-name"] == id { 29 30 l := c.log.With("podNamespace", pod.Namespace, "podName", pod.Name, "podStatus", pod.Status) 31 32 switch pod.Status.Phase { 33 34 case corev1.PodRunning: 35 l.Debug("tailing pod logs: immediately") 36 return c.TailPodLogs(namespace, pod, logs) 37 38 case corev1.PodFailed: 39 err := fmt.Errorf("can't get pod logs, pod failed: %s/%s", pod.Namespace, pod.Name) 40 l.Errorw(err.Error()) 41 return err 42 43 default: 44 l.Debugw("tailing job logs: waiting for pod to be ready") 45 if err = wait.PollUntilContextTimeout(ctx, pollInterval, c.podStartTimeout, true, executor.IsPodLoggable(c.clientSet, pod.Name, namespace)); err != nil { 46 l.Errorw("poll immediate error when tailing logs", "error", err) 47 return err 48 } 49 50 l.Debug("tailing pod logs") 51 return c.TailPodLogs(namespace, pod, logs) 52 } 53 } 54 } 55 return 56 } 57 58 func (c *ContainerExecutor) TailPodLogs(namespace string, pod corev1.Pod, logs chan []byte) (err error) { 59 var containers []string 60 for _, container := range pod.Spec.InitContainers { 61 containers = append(containers, container.Name) 62 } 63 64 for _, container := range pod.Spec.Containers { 65 containers = append(containers, container.Name) 66 } 67 68 l := c.log.With("method", "tailPodLogs", "containers", len(containers)) 69 70 wg := sync.WaitGroup{} 71 72 wg.Add(len(containers)) 73 ctx := context.Background() 74 75 for _, container := range containers { 76 go func(container string) { 77 defer wg.Done() 78 podLogOptions := corev1.PodLogOptions{ 79 Follow: true, 80 Container: container, 81 } 82 83 podLogRequest := c.clientSet.CoreV1(). 84 Pods(namespace). 85 GetLogs(pod.Name, &podLogOptions) 86 87 stream, err := podLogRequest.Stream(ctx) 88 if err != nil { 89 l.Errorw("stream error", "error", err) 90 return 91 } 92 93 reader := bufio.NewReader(stream) 94 95 for { 96 b, err := utils.ReadLongLine(reader) 97 if err != nil { 98 if err == io.EOF { 99 err = nil 100 } else { 101 l.Errorw("scanner error", "error", err) 102 } 103 break 104 } 105 logs <- b 106 l.Debugw("log chunk pushed", "out", string(b), "pod", pod.Name) 107 } 108 }(container) 109 } 110 111 go func() { 112 defer close(logs) 113 l.Debugw("log stream - waiting for all containers to finish", "containers", containers) 114 wg.Wait() 115 l.Debugw("log stream - finished") 116 }() 117 118 return 119 }