github.com/telepresenceio/telepresence/v2@v2.20.0-pro.6.0.20240517030216-236ea954e789/pkg/client/userd/trafficmgr/gather_logs.go (about)

     1  package trafficmgr
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  	"sync"
    11  
    12  	core "k8s.io/api/core/v1"
    13  	meta "k8s.io/apimachinery/pkg/apis/meta/v1"
    14  	"k8s.io/apimachinery/pkg/labels"
    15  	typed "k8s.io/client-go/kubernetes/typed/core/v1"
    16  	"sigs.k8s.io/yaml"
    17  
    18  	"github.com/datawire/dlib/dlog"
    19  	"github.com/datawire/k8sapi/pkg/k8sapi"
    20  	"github.com/telepresenceio/telepresence/rpc/v2/connector"
    21  	"github.com/telepresenceio/telepresence/v2/pkg/agentconfig"
    22  	"github.com/telepresenceio/telepresence/v2/pkg/agentmap"
    23  )
    24  
    25  // getPodLog obtains the log and optionally the YAML for a given pod and stores it in
    26  // a file named <POD NAME>.<POD NAMESPACE>.log (and .yaml, if applicable) under the
    27  // given exportDir directory. An entry with the relative filename as a key is created
    28  // in the result map. The entry will either contain the string "ok" or an error when
    29  // the log or yaml for some reason could not be written to the file.
    30  func getPodLog(ctx context.Context, exportDir string, result *sync.Map, podsAPI typed.PodInterface, pod *core.Pod, container string, podYAML, agent bool) {
    31  	if !agentmap.IsPodRunning(pod) || agent && agentmap.AgentContainer(pod) == nil {
    32  		return
    33  	}
    34  	podLog := pod.Name + "." + pod.Namespace + ".log"
    35  	req := podsAPI.GetLogs(pod.Name, &core.PodLogOptions{Container: container})
    36  	logStream, err := req.Stream(ctx)
    37  	if err != nil {
    38  		err = fmt.Errorf("failed to get log for %s.%s: %w", pod.Name, pod.Namespace, err)
    39  		dlog.Error(ctx, err)
    40  		result.Store(podLog, err.Error())
    41  		return
    42  	}
    43  	defer logStream.Close()
    44  
    45  	f, err := os.Create(filepath.Join(exportDir, podLog))
    46  	if err != nil {
    47  		dlog.Error(ctx, err)
    48  		result.Store(podLog, err.Error())
    49  		return
    50  	}
    51  	defer f.Close()
    52  
    53  	if _, err = io.Copy(f, logStream); err != nil {
    54  		err = fmt.Errorf("failed writing log to buffer: %w", err)
    55  		dlog.Error(ctx, err)
    56  		result.Store(podLog, err.Error())
    57  		return
    58  	}
    59  	result.Store(podLog, "ok")
    60  
    61  	// Get the pod yaml if the user asked for it
    62  	if podYAML {
    63  		var b []byte
    64  		podYaml := pod.Name + "." + pod.Namespace + ".yaml"
    65  		if b, err = yaml.Marshal(pod); err != nil {
    66  			err = fmt.Errorf("failed marshaling pod yaml: %w", err)
    67  			dlog.Error(ctx, err)
    68  			result.Store(podYaml, err.Error())
    69  			return
    70  		}
    71  		if err = os.WriteFile(filepath.Join(exportDir, podYaml), b, 0o666); err != nil {
    72  			result.Store(podYaml, err.Error())
    73  			return
    74  		}
    75  		result.Store(podYaml, "ok")
    76  	}
    77  }
    78  
    79  func (s *session) ForeachAgentPod(ctx context.Context, fn func(context.Context, typed.PodInterface, *core.Pod), filter func(*core.Pod) bool) error {
    80  	hasContainer := func(pod *core.Pod) bool {
    81  		if filter == nil || filter(pod) {
    82  			cns := pod.Spec.Containers
    83  			for c := range cns {
    84  				if cns[c].Name == agentconfig.ContainerName {
    85  					return true
    86  				}
    87  			}
    88  		}
    89  		return false
    90  	}
    91  
    92  	coreAPI := k8sapi.GetK8sInterface(ctx).CoreV1()
    93  	for _, ns := range s.GetCurrentNamespaces(true) {
    94  		podsAPI := coreAPI.Pods(ns)
    95  		podList, err := podsAPI.List(ctx, meta.ListOptions{})
    96  		if err != nil {
    97  			return err
    98  		}
    99  		pods := podList.Items
   100  		podsWithContainer := make([]*core.Pod, 0, len(pods))
   101  		for i := range pods {
   102  			pod := &pods[i]
   103  			if hasContainer(pod) {
   104  				podsWithContainer = append(podsWithContainer, pod)
   105  			}
   106  		}
   107  		wg := sync.WaitGroup{}
   108  		wg.Add(len(podsWithContainer))
   109  		for _, pod := range podsWithContainer {
   110  			go func(pod *core.Pod) {
   111  				defer wg.Done()
   112  				fn(ctx, podsAPI, pod)
   113  			}(pod)
   114  		}
   115  		wg.Wait()
   116  	}
   117  
   118  	return nil
   119  }
   120  
   121  // GatherLogs acquires the logs for the traffic-manager and/or traffic-agents specified by the
   122  // connector.LogsRequest and returns them to the caller.
   123  func (s *session) GatherLogs(ctx context.Context, request *connector.LogsRequest) (*connector.LogsResponse, error) {
   124  	exportDir := request.ExportDir
   125  	coreAPI := k8sapi.GetK8sInterface(ctx).CoreV1()
   126  	resp := &connector.LogsResponse{}
   127  	result := sync.Map{}
   128  
   129  	if !strings.EqualFold(request.Agents, "none") {
   130  		err := s.ForeachAgentPod(ctx, func(ctx context.Context, podsAPI typed.PodInterface, pod *core.Pod) {
   131  			podAndNs := fmt.Sprintf("%s.%s", pod.Name, pod.Namespace)
   132  			dlog.Debugf(ctx, "gathering logs for %s, yaml = %t", podAndNs, request.GetPodYaml)
   133  			getPodLog(ctx, exportDir, &result, podsAPI, pod, agentconfig.ContainerName, request.GetPodYaml, true)
   134  		}, func(pod *core.Pod) bool {
   135  			return strings.EqualFold(request.Agents, "all") || strings.Contains(pod.Name, request.Agents)
   136  		})
   137  		if err != nil {
   138  			resp.Error = err.Error()
   139  			return resp, nil
   140  		}
   141  	}
   142  
   143  	// We want to get the traffic-manager log *last* so that if we generate
   144  	// any errors in the traffic-manager getting the traffic-agent pods, we
   145  	// want those logs to appear in what we export
   146  	if request.TrafficManager {
   147  		ns := s.GetManagerNamespace()
   148  		podsAPI := coreAPI.Pods(ns)
   149  		selector := labels.SelectorFromSet(labels.Set{
   150  			"app":          "traffic-manager",
   151  			"telepresence": "manager",
   152  		})
   153  		podList, err := podsAPI.List(ctx, meta.ListOptions{LabelSelector: selector.String()})
   154  		switch {
   155  		case err != nil:
   156  			err = fmt.Errorf("failed to gather logs for traffic manager in namespace %s: %w", ns, err)
   157  			dlog.Error(ctx, err)
   158  			resp.Error = err.Error()
   159  		case len(podList.Items) == 1:
   160  			pod := &podList.Items[0]
   161  			podAndNs := fmt.Sprintf("%s.%s", pod.Name, ns)
   162  			dlog.Debugf(ctx, "gathering logs for %s, yaml = %t", podAndNs, request.GetPodYaml)
   163  			getPodLog(ctx, exportDir, &result, podsAPI, pod, "traffic-manager", request.GetPodYaml, false)
   164  		case len(podList.Items) > 1:
   165  			err = fmt.Errorf("multiple traffic managers found in namespace %s using selector %s", ns, selector.String())
   166  			dlog.Error(ctx, err)
   167  			resp.Error = err.Error()
   168  		default:
   169  			err := fmt.Errorf("no traffic manager found in namespace %s using selector %s", ns, selector.String())
   170  			dlog.Error(ctx, err)
   171  			resp.Error = err.Error()
   172  		}
   173  	}
   174  	pi := make(map[string]string)
   175  	result.Range(func(k, v any) bool {
   176  		pi[k.(string)] = v.(string)
   177  		return true
   178  	})
   179  	resp.PodInfo = pi
   180  	return resp, nil
   181  }