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 }