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  }