-
Notifications
You must be signed in to change notification settings - Fork 88
/
report.go
155 lines (131 loc) · 4.21 KB
/
report.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
package reporting
import (
"context"
"encoding/base64"
"encoding/json"
"sync"
"github.com/pkg/errors"
kotsadmtypes "github.com/replicatedhq/kots/pkg/kotsadm/types"
"github.com/replicatedhq/kots/pkg/util"
corev1 "k8s.io/api/core/v1"
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)
const (
ReportSecretNameFormat = "kotsadm-%s-report"
ReportSecretKey = "report"
ReportEventLimit = 4000
ReportSizeLimit = 1 * 1024 * 1024 // 1MB
)
type ReportType string
const (
ReportTypeInstance ReportType = "instance"
ReportTypePreflight ReportType = "preflight"
)
type Report interface {
GetType() ReportType
GetSecretName(appSlug string) string
GetSecretKey() string
AppendEvents(report Report) error
GetEventLimit() int
GetMtx() *sync.Mutex
}
var _ Report = &InstanceReport{}
var _ Report = &PreflightReport{}
func AppendReport(clientset kubernetes.Interface, namespace string, appSlug string, report Report) error {
report.GetMtx().Lock()
defer report.GetMtx().Unlock()
existingSecret, err := clientset.CoreV1().Secrets(namespace).Get(context.TODO(), report.GetSecretName(appSlug), metav1.GetOptions{})
if err != nil && !kuberneteserrors.IsNotFound(err) {
return errors.Wrap(err, "failed to get report secret")
} else if kuberneteserrors.IsNotFound(err) {
data, err := EncodeReport(report)
if err != nil {
return errors.Wrap(err, "failed to encode report")
}
secret := &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: report.GetSecretName(appSlug),
Namespace: namespace,
Labels: kotsadmtypes.GetKotsadmLabels(),
},
Data: map[string][]byte{
report.GetSecretKey(): data,
},
}
_, err = clientset.CoreV1().Secrets(namespace).Create(context.TODO(), secret, metav1.CreateOptions{})
if err != nil {
return errors.Wrap(err, "failed to create report secret")
}
return nil
}
if existingSecret.Data == nil {
existingSecret.Data = map[string][]byte{}
}
var existingReport Report
if existingSecret.Data[report.GetSecretKey()] != nil {
existingReport, err = DecodeReport(existingSecret.Data[report.GetSecretKey()], report.GetType())
if err != nil {
return errors.Wrap(err, "failed to load existing report")
}
if err := existingReport.AppendEvents(report); err != nil {
return errors.Wrap(err, "failed to append events to existing report")
}
} else {
// secret exists but doesn't have the report key, so just use the report that was passed in
existingReport = report
}
data, err := EncodeReport(existingReport)
if err != nil {
return errors.Wrap(err, "failed to encode existing report")
}
existingSecret.Data[report.GetSecretKey()] = data
_, err = clientset.CoreV1().Secrets(namespace).Update(context.TODO(), existingSecret, metav1.UpdateOptions{})
if err != nil {
return errors.Wrap(err, "failed to update report secret")
}
return nil
}
func EncodeReport(r Report) ([]byte, error) {
data, err := json.Marshal(r)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal report")
}
compressedData, err := util.GzipData(data)
if err != nil {
return nil, errors.Wrap(err, "failed to gzip report")
}
encodedData := base64.StdEncoding.EncodeToString(compressedData)
return []byte(encodedData), nil
}
func DecodeReport(encodedData []byte, reportType ReportType) (Report, error) {
decodedData, err := base64.StdEncoding.DecodeString(string(encodedData))
if err != nil {
return nil, errors.Wrap(err, "failed to decode report")
}
decompressedData, err := util.GunzipData(decodedData)
if err != nil {
return nil, errors.Wrap(err, "failed to gunzip report")
}
var r Report
switch reportType {
case ReportTypeInstance:
r = &InstanceReport{}
if err := json.Unmarshal(decompressedData, r); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal instance report")
}
case ReportTypePreflight:
r = &PreflightReport{}
if err := json.Unmarshal(decompressedData, r); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal preflight report")
}
default:
return nil, errors.Errorf("unknown report type %q", reportType)
}
return r, nil
}