github.com/smartcontractkit/chainlink-testing-framework/libs@v0.0.0-20240227141906-ec710b4eb1a3/k8s/environment/artifacts.go (about) 1 package environment 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 "os" 9 "path/filepath" 10 "strings" 11 12 "github.com/smartcontractkit/chainlink-testing-framework/libs/k8s/client" 13 14 "github.com/rs/zerolog/log" 15 coreV1 "k8s.io/api/core/v1" 16 metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" 17 "k8s.io/client-go/kubernetes/scheme" 18 clientV1 "k8s.io/client-go/kubernetes/typed/core/v1" 19 "k8s.io/client-go/tools/remotecommand" 20 ) 21 22 // Artifacts is an artifacts dumping structure that copies logs and database dumps for all deployed pods 23 type Artifacts struct { 24 Namespace string 25 DBName string 26 Client *client.K8sClient 27 podsClient clientV1.PodInterface 28 } 29 30 // NewArtifacts create new artifacts instance for provided environment 31 func NewArtifacts(client *client.K8sClient, namespace string) (*Artifacts, error) { 32 return &Artifacts{ 33 Namespace: namespace, 34 Client: client, 35 podsClient: client.ClientSet.CoreV1().Pods(namespace), 36 }, nil 37 } 38 39 // DumpTestResult dumps all pods logs and db dump in a separate test dir 40 func (a *Artifacts) DumpTestResult(testDir string, dbName string) error { 41 a.DBName = dbName 42 if err := MkdirIfNotExists(testDir); err != nil { 43 return err 44 } 45 return a.writePodArtifacts(testDir) 46 } 47 48 func (a *Artifacts) writePodArtifacts(testDir string) error { 49 log.Info(). 50 Str("Test", testDir). 51 Msg("Writing test artifacts") 52 podsList, err := a.podsClient.List(context.Background(), metaV1.ListOptions{}) 53 if err != nil { 54 log.Err(err). 55 Str("Namespace", a.Namespace). 56 Msg("Error retrieving pod list from K8s environment") 57 return err 58 } 59 for _, pod := range podsList.Items { 60 log.Info(). 61 Str("Pod", pod.Name). 62 Msg("Writing pod artifacts") 63 appName := pod.Labels[client.AppLabel] 64 instance := pod.Labels["instance"] 65 appDir := filepath.Join(testDir, fmt.Sprintf("%s_%s", appName, instance)) 66 if err := MkdirIfNotExists(appDir); err != nil { 67 return err 68 } 69 err = a.writePodLogs(pod, appDir) 70 if err != nil { 71 log.Err(err). 72 Str("Namespace", a.Namespace). 73 Str("Pod", pod.Name). 74 Msg("Error writing logs for pod") 75 } 76 } 77 return nil 78 } 79 80 func (a *Artifacts) dumpDB(pod coreV1.Pod, container coreV1.Container) (string, error) { 81 postRequestBase := a.Client.ClientSet.CoreV1().RESTClient().Post(). 82 Namespace(pod.Namespace).Resource("pods").Name(pod.Name).SubResource("exec") 83 exportDBRequest := postRequestBase.VersionedParams( 84 &coreV1.PodExecOptions{ 85 Container: container.Name, 86 Command: []string{"/bin/sh", "-c", "pg_dump", a.DBName}, 87 Stdin: true, 88 Stdout: true, 89 Stderr: true, 90 TTY: false, 91 }, scheme.ParameterCodec) 92 exec, err := remotecommand.NewSPDYExecutor(a.Client.RESTConfig, "POST", exportDBRequest.URL()) 93 if err != nil { 94 return "", err 95 } 96 outBuff, errBuff := &bytes.Buffer{}, &bytes.Buffer{} 97 err = exec.Stream(remotecommand.StreamOptions{ 98 Stdin: &bytes.Reader{}, 99 Stdout: outBuff, 100 Stderr: errBuff, 101 Tty: false, 102 }) 103 if err != nil || errBuff.Len() > 0 { 104 return "", fmt.Errorf("error in dumping DB contents | STDOUT: %v | STDERR: %v", outBuff.String(), 105 errBuff.String()) 106 } 107 return outBuff.String(), err 108 } 109 110 func (a *Artifacts) writePostgresDump(podDir string, pod coreV1.Pod, cont coreV1.Container) error { 111 dumpContents, err := a.dumpDB(pod, cont) 112 if err != nil { 113 return err 114 } 115 logFile, err := os.Create(filepath.Join(podDir, fmt.Sprintf("%s_dump.sql", cont.Name))) 116 if err != nil { 117 return err 118 } 119 _, err = logFile.WriteString(dumpContents) 120 if err != nil { 121 return err 122 } 123 return logFile.Close() 124 } 125 126 func (a *Artifacts) writeContainerLogs(podDir string, pod coreV1.Pod, cont coreV1.Container) error { 127 logFile, err := os.Create(filepath.Join(podDir, cont.Name) + ".log") 128 if err != nil { 129 return err 130 } 131 podLogRequest := a.podsClient.GetLogs(pod.Name, &coreV1.PodLogOptions{Container: cont.Name}) 132 podLogs, err := podLogRequest.Stream(context.Background()) 133 if err != nil { 134 return err 135 } 136 buf := new(bytes.Buffer) 137 _, err = io.Copy(buf, podLogs) 138 if err != nil { 139 return err 140 } 141 _, err = logFile.Write(buf.Bytes()) 142 if err != nil { 143 return err 144 } 145 146 if err = logFile.Close(); err != nil { 147 return err 148 } 149 return podLogs.Close() 150 } 151 152 // Writes logs for each container in a pod 153 func (a *Artifacts) writePodLogs(pod coreV1.Pod, appDir string) error { 154 for _, c := range pod.Spec.Containers { 155 log.Info(). 156 Str("Container", c.Name). 157 Msg("Writing container artifacts") 158 if err := a.writeContainerLogs(appDir, pod, c); err != nil { 159 return err 160 } 161 if strings.Contains(c.Image, "postgres") { 162 if err := a.writePostgresDump(appDir, pod, c); err != nil { 163 return err 164 } 165 } 166 } 167 return nil 168 } 169 170 func MkdirIfNotExists(dirName string) error { 171 if _, err := os.Stat(dirName); os.IsNotExist(err) { 172 if err = os.MkdirAll(dirName, os.ModePerm); err != nil { 173 return fmt.Errorf("failed to create directory: %s err: %w", dirName, err) 174 } 175 } 176 return nil 177 }