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