/
runtime_kcp.go
335 lines (305 loc) · 10.5 KB
/
runtime_kcp.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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
package runtime
import (
"context"
"encoding/json"
"fmt"
"path/filepath"
"strings"
"time"
log "github.com/sirupsen/logrus"
v1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
)
/*
ConfigMap example:
Name: "userID",
Namespace: "kcp-system",
Label: "service=kubeconfig",
Annotation:map[string]string
"role=L2L3ROLE"
"tenant=tenantID"
Data:map[string]string
"runtimeid-a" : "starttime"
"runtimeid-b" : "starttime"
*/
const KcpNamespace string = "kcp-system"
const ExpireTime time.Duration = 24 * time.Hour
type JsonPatchType struct {
Op string `json:"op"`
Path string `json:"path"`
Value string `json:"value"`
}
func SetupConfigMap() error {
configMapList, err := getConfigMapList()
if configMapList == nil && err == nil {
log.Info("No Timer will be setup.")
return nil
} else if err != nil {
log.Errorf("Failed to setup timer.")
return err
}
for _, configMap := range (*configMapList).Items {
userID := configMap.ObjectMeta.Name
role := configMap.ObjectMeta.Annotations["role"]
tenantID := configMap.ObjectMeta.Annotations["tenant"]
for runtimeID, startTimeString := range configMap.Data {
log.Infof("Found ConfigMap for runtime %s user %s.", runtimeID, userID)
//delete ConfigMap if shoot no longer exists
rawConfig, err := GetRawConfig(runtimeID)
if k8serrors.IsNotFound(err) {
coreClientset, err := GetK8sClient()
if err != nil {
log.Errorf("Failed to create core client set.")
return err
}
err = cleanConfigMap(coreClientset, userID, runtimeID)
if err != nil {
log.Errorf("Failed to clean ConfigMap for user %s runtime %s, %s", userID, runtimeID, err.Error())
return err
}
continue
} else if err != nil {
log.Errorf("Failed to fetch Config for runtime %s.", runtimeID)
return err
}
rtc, err := NewRuntimeClient(rawConfig, userID, role, tenantID)
if err != nil {
log.Errorf("Failed to create runtime client.")
return err
}
startTime, err := time.Parse("2006-01-02 15:04:05 +0000 UTC", startTimeString)
if err != nil {
log.Errorf("Failed to convert start time.")
return err
}
go rtc.SetupTimer(startTime, runtimeID)
}
}
return nil
}
func GetK8sConfig() (*restclient.Config, error) {
k8sConfig, err := restclient.InClusterConfig()
if err != nil {
log.Warnf("Failed to read in cluster config: %s", err.Error())
log.Info("Trying to initialize with local config")
home := homedir.HomeDir()
k8sConfPath := filepath.Join(home, ".kube", "config")
k8sConfig, err = clientcmd.BuildConfigFromFlags("", k8sConfPath)
if err != nil {
log.Errorf("Failed to read k8s in-cluster configuration, %s", err.Error())
return nil, err
}
}
return k8sConfig, nil
}
func GetK8sClient() (kubernetes.Interface, error) {
k8sconfig, err := GetK8sConfig()
if err != nil {
return nil, err
}
clientset, err := kubernetes.NewForConfig(k8sconfig)
if err != nil {
log.Errorf("Failed to create k8s core client, %s", err.Error())
return nil, err
}
return clientset, err
}
func (rtc *RuntimeClient) SetupTimer(startTime time.Time, runtimeID string) {
userID := rtc.User.ServiceAccountName
endTime := startTime.Add(ExpireTime)
duration := time.Until(endTime)
if duration >= 0 {
timer := time.NewTimer(duration)
<-timer.C
defer timer.Stop()
}
//After timer, check start time, if changed, clean up SA and ConfigMap
timeBefore := strings.Split(startTime.String(), " m=")[0]
cm, err := rtc.KcpK8s.CoreV1().ConfigMaps(KcpNamespace).Get(context.Background(), userID, metav1.GetOptions{})
if err != nil {
log.Errorf("Failed to get ConfigMap for user %s runtime %s, %s", userID, runtimeID, err.Error())
return
}
timeAfter := cm.Data[runtimeID]
if timeBefore != timeAfter {
log.Infof("StartTime changed for runtime %s for user %s, skip clean up.", runtimeID, userID)
return
}
log.Infof("Start to clean everything for runtime %s for user %s.", runtimeID, userID)
rtc.RollbackE.Data = append(rtc.RollbackE.Data, SA)
rtc.RollbackE.Data = append(rtc.RollbackE.Data, ClusterRole)
rtc.RollbackE.Data = append(rtc.RollbackE.Data, ClusterRoleBinding)
err = rtc.Cleaner()
if err != nil {
log.Errorf("Failed to clean runtime %s for user %s.", runtimeID, userID)
return
}
err = rtc.UpdateConfigMap(runtimeID)
if err != nil {
log.Errorf("Failed to clean ConfigMap for runtime %s user %s", runtimeID, userID)
return
}
}
func (rtc *RuntimeClient) UpdateConfigMap(runtimeID string) error {
log.Infof("Trying to remove expired information for runtime %s.", runtimeID)
userID := rtc.User.ServiceAccountName
//checking configmap existance
cm, err := rtc.KcpK8s.CoreV1().ConfigMaps(KcpNamespace).Get(context.Background(), userID, metav1.GetOptions{})
if err != nil {
log.Errorf("Failed to get ConfigMap for user %s runtime %s, %s", userID, runtimeID, err.Error())
return err
}
if len(cm.Data[runtimeID]) == 0 {
log.Infof("Configmap of runtime %s already deleted.", runtimeID)
return nil
}
err = cleanConfigMap(rtc.KcpK8s, userID, runtimeID)
if err != nil {
log.Errorf("Failed to clean ConfigMap for user %s runtime %s, %s", userID, runtimeID, err.Error())
return err
}
return nil
}
func (rtc *RuntimeClient) DeployConfigMap(runtimeID string, L2L3OperatorRole string, startTime time.Time) error {
userID := rtc.User.ServiceAccountName
tenantID := rtc.User.TenantID
startTimeString := strings.Split(startTime.String(), " m=")[0]
log.Info("Checking if the user exists")
cm, err := rtc.KcpK8s.CoreV1().ConfigMaps(KcpNamespace).Get(context.Background(), userID, metav1.GetOptions{})
if k8serrors.IsNotFound(err) {
log.Info("User doens't exist. Trying to create configmap.")
configmap := &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: userID,
Namespace: KcpNamespace,
Labels: map[string]string{"service": "kubeconfig"},
Annotations: map[string]string{"role": L2L3OperatorRole, "tenant": tenantID},
},
Data: map[string]string{runtimeID: startTimeString},
}
_, err = rtc.KcpK8s.CoreV1().ConfigMaps(KcpNamespace).Create(context.Background(), configmap, metav1.CreateOptions{})
if err != nil && !k8serrors.IsAlreadyExists(err) {
log.Errorf("Failed to create ConfigMap for user %s runtime %s, %s", userID, runtimeID, err.Error())
return err
}
log.Infof("Configmap created for runtime %s user %s.", runtimeID, userID)
} else if err != nil {
log.Errorf("Failed to get ConfigMap for user %s runtime %s, %s", userID, runtimeID, err.Error())
return err
} else {
log.Info("User already exist. Trying to update ConfigMap.")
var patches []*JsonPatchType
var patch *JsonPatchType
path := "/data/" + runtimeID
if len(cm.Data[runtimeID]) != 0 {
log.Info("Runtime already exist. Trying to update expire time.")
patch = &JsonPatchType{
Op: "replace",
Path: path,
Value: startTimeString,
}
} else {
log.Info("Runtime not exist. Trying to create new entry.")
patch = &JsonPatchType{
Op: "add",
Path: path,
Value: startTimeString,
}
}
patches = append(patches, patch)
payload, err := json.Marshal(patches)
if err != nil {
log.Errorf("Failed to marshal patch for user %s runtime %s, %s", userID, runtimeID, err.Error())
return err
}
_, err = rtc.KcpK8s.CoreV1().ConfigMaps(KcpNamespace).Patch(context.Background(), userID, types.JSONPatchType, payload, metav1.PatchOptions{})
if err != nil {
log.Errorf("Failed to update ConfigMap for user %s runtime %s, %s", userID, runtimeID, err.Error())
return err
}
log.Infof("Configmap updated for runtime %s user %s.", runtimeID, userID)
}
return nil
}
func getConfigMapList() (*v1.ConfigMapList, error) {
coreClientset, err := GetK8sClient()
if err != nil {
log.Errorf("Failed to get kcp k8s client.")
return nil, err
}
cmlist, err := coreClientset.CoreV1().ConfigMaps(KcpNamespace).List(context.Background(), metav1.ListOptions{LabelSelector: "service=kubeconfig"})
if k8serrors.IsNotFound(err) {
log.Info("All ConfigMap cleaned up.")
return nil, nil
} else if err != nil {
log.Errorf("Failed to get ConfigMap list: %s", err.Error())
return nil, err
}
return cmlist, nil
}
func cleanConfigMap(coreClientset kubernetes.Interface, userID string, runtimeID string) error {
var patches []*JsonPatchType
path := "/data/" + runtimeID
patch := &JsonPatchType{
Op: "remove",
Path: path,
}
patches = append(patches, patch)
payload, err := json.Marshal(patches)
if err != nil {
log.Errorf("Failed to marshal patch, %s", err.Error())
return err
}
_, err = coreClientset.CoreV1().ConfigMaps(KcpNamespace).Patch(context.Background(), userID, types.JSONPatchType, payload, metav1.PatchOptions{})
if err != nil {
log.Errorf("Failed to update ConfigMap, %s", err.Error())
return err
}
log.Infof("Succeeded in cleaning up everything for runtime %s user %s", runtimeID, userID)
//remove user ConfigMap if no runtime left
cm, err := coreClientset.CoreV1().ConfigMaps(KcpNamespace).Get(context.Background(), userID, metav1.GetOptions{})
if err != nil {
log.Errorf("Failed to get ConfigMap for user %s runtime %s, %s", userID, runtimeID, err.Error())
return err
}
if len(cm.Data) == 0 {
log.Infof("No runtime left for user %s, start to remove ConfigMap.", userID)
err = coreClientset.CoreV1().ConfigMaps(KcpNamespace).Delete(context.Background(), userID, metav1.DeleteOptions{})
if err != nil {
log.Errorf("Failed to delete ConfigMap for user %s", userID)
return err
}
log.Infof("Succeeded in removing ConfigMap for user %s.", userID)
}
return nil
}
func GetRawConfig(runtimeID string) ([]byte, error) {
coreClientset, err := GetK8sClient()
if err != nil {
log.Errorf("Failed to get kcp k8s client.")
return nil, err
}
secretList, err := coreClientset.CoreV1().Secrets(KcpNamespace).List(context.Background(), metav1.ListOptions{LabelSelector: fmt.Sprintf("kyma-project.io/runtime-id=%s", runtimeID)})
if len((*secretList).Items) == 0 {
log.Infof("No secret found for runtime %s.", runtimeID)
return nil, k8serrors.NewNotFound(schema.GroupResource{}, "")
} else if len((*secretList).Items) > 1 {
log.Warnf("More than one secrets found for runtime %s.", runtimeID)
} else if err != nil {
log.Errorf("Failed to filter secret for runtime %s: %s", runtimeID, err.Error())
return nil, err
}
var secret []byte
for _, item := range (*secretList).Items {
secret = item.Data["config"]
break
}
return secret, nil
}