1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package common
17
18 import (
19 "context"
20 "crypto/rand"
21 "fmt"
22 "math/big"
23 "net/http"
24 "sort"
25 "strings"
26
27 "github.com/gin-gonic/gin"
28 "github.com/go-logr/logr"
29 "github.com/pkg/errors"
30 v1 "k8s.io/api/core/v1"
31 "sigs.k8s.io/controller-runtime/pkg/client"
32
33 "github.com/chaos-mesh/chaos-mesh/api/v1alpha1"
34 "github.com/chaos-mesh/chaos-mesh/pkg/clientpool"
35 "github.com/chaos-mesh/chaos-mesh/pkg/config"
36 apiservertypes "github.com/chaos-mesh/chaos-mesh/pkg/dashboard/apiserver/types"
37 u "github.com/chaos-mesh/chaos-mesh/pkg/dashboard/apiserver/utils"
38 "github.com/chaos-mesh/chaos-mesh/pkg/selector/generic/namespace"
39 "github.com/chaos-mesh/chaos-mesh/pkg/selector/physicalmachine"
40 "github.com/chaos-mesh/chaos-mesh/pkg/selector/pod"
41 )
42
43 const (
44 roleManager = "manager"
45 roleViewer = "viewer"
46
47 serviceAccountTemplate = `kind: ServiceAccount
48 apiVersion: v1
49 metadata:
50 namespace: %s
51 name: %s
52 `
53 roleTemplate = `kind: Role
54 apiVersion: rbac.authorization.k8s.io/v1
55 metadata:
56 namespace: %s
57 name: %s
58 rules:
59 - apiGroups: [""]
60 resources: ["pods", "namespaces"]
61 verbs: ["get", "watch", "list"]
62 - apiGroups: ["chaos-mesh.org"]
63 resources: [ "*" ]
64 verbs: [%s]
65 `
66 clusterRoleTemplate = `kind: ClusterRole
67 apiVersion: rbac.authorization.k8s.io/v1
68 metadata:
69 name: %s
70 rules:
71 - apiGroups: [""]
72 resources: ["pods", "namespaces"]
73 verbs: ["get", "watch", "list"]
74 - apiGroups: ["chaos-mesh.org"]
75 resources: [ "*" ]
76 verbs: [%s]
77 `
78 roleBindingTemplate = `apiVersion: rbac.authorization.k8s.io/v1
79 kind: RoleBinding
80 metadata:
81 name: %s
82 namespace: %s
83 subjects:
84 - kind: ServiceAccount
85 name: %s
86 namespace: %s
87 roleRef:
88 kind: Role
89 name: %s
90 apiGroup: rbac.authorization.k8s.io
91 `
92 clusterRoleBindingTemplate = `apiVersion: rbac.authorization.k8s.io/v1
93 kind: ClusterRoleBinding
94 metadata:
95 name: %s
96 subjects:
97 - kind: ServiceAccount
98 name: %s
99 namespace: %s
100 roleRef:
101 kind: ClusterRole
102 name: %s
103 apiGroup: rbac.authorization.k8s.io
104 `
105 )
106
107
108 type Service struct {
109
110 kubeCli client.Client
111 conf *config.ChaosDashboardConfig
112 logger logr.Logger
113 }
114
115
116 func NewService(
117 conf *config.ChaosDashboardConfig,
118 kubeCli client.Client,
119 logger logr.Logger,
120 ) *Service {
121 return &Service{
122 conf: conf,
123 kubeCli: kubeCli,
124 logger: logger.WithName("common-api"),
125 }
126 }
127
128
129 func Register(r *gin.RouterGroup, s *Service) {
130 endpoint := r.Group("/common")
131
132 endpoint.POST("/pods", s.listPods)
133 endpoint.GET("/namespaces", s.listNamespaces)
134 endpoint.GET("/chaos-available-namespaces", s.getChaosAvailableNamespaces)
135 endpoint.GET("/kinds", s.getKinds)
136 endpoint.GET("/labels", s.getLabels)
137 endpoint.GET("/annotations", s.getAnnotations)
138 endpoint.GET("/config", s.getConfig)
139 endpoint.GET("/rbac-config", s.getRbacConfig)
140 endpoint.POST("/physicalmachines", s.listPhysicalMachines)
141 endpoint.GET("/physicalmachine-labels", s.getPhysicalMachineLabels)
142 endpoint.GET("/physicalmachine-annotations", s.getPhysicalMachineAnnotations)
143 }
144
145
146
147
148
149
150
151
152
153 func (s *Service) listPods(c *gin.Context) {
154 kubeCli, err := clientpool.ExtractTokenAndGetClient(c.Request.Header)
155 if err != nil {
156 _ = c.Error(u.ErrBadRequest.WrapWithNoMessage(err))
157 return
158 }
159
160 selector := v1alpha1.PodSelectorSpec{}
161 if err := c.ShouldBindJSON(&selector); err != nil {
162 c.Status(http.StatusBadRequest)
163 _ = c.Error(u.ErrBadRequest.WrapWithNoMessage(err))
164 return
165 }
166 ctx := context.TODO()
167 filteredPods, err := pod.SelectPods(ctx, kubeCli, nil, selector, s.conf.ClusterScoped, s.conf.TargetNamespace, s.conf.EnableFilterNamespace)
168 if err != nil {
169 c.Status(http.StatusInternalServerError)
170 _ = c.Error(u.ErrInternalServer.WrapWithNoMessage(err))
171 return
172 }
173
174 pods := make([]apiservertypes.Pod, 0, len(filteredPods))
175 for _, pod := range filteredPods {
176 pods = append(pods, apiservertypes.Pod{
177 Name: pod.Name,
178 IP: pod.Status.PodIP,
179 Namespace: pod.Namespace,
180 State: string(pod.Status.Phase),
181 })
182 }
183
184 c.JSON(http.StatusOK, pods)
185 }
186
187
188
189
190
191
192
193
194
195 func (s *Service) listNamespaces(c *gin.Context) {
196 var namespaces sort.StringSlice
197
198 var nsList v1.NamespaceList
199 if err := s.kubeCli.List(context.Background(), &nsList); err != nil {
200 c.Status(http.StatusInternalServerError)
201 _ = c.Error(u.ErrInternalServer.WrapWithNoMessage(err))
202 return
203 }
204 namespaces = make(sort.StringSlice, 0, len(nsList.Items))
205 for _, ns := range nsList.Items {
206 namespaces = append(namespaces, ns.Name)
207 }
208
209 sort.Sort(namespaces)
210 c.JSON(http.StatusOK, namespaces)
211 }
212
213
214
215
216
217
218
219
220 func (s *Service) getChaosAvailableNamespaces(c *gin.Context) {
221 var namespaces sort.StringSlice
222
223 if s.conf.ClusterScoped {
224 var nsList v1.NamespaceList
225 if err := s.kubeCli.List(context.Background(), &nsList); err != nil {
226 c.Status(http.StatusInternalServerError)
227 _ = c.Error(u.ErrInternalServer.WrapWithNoMessage(err))
228 return
229 }
230 namespaces = make(sort.StringSlice, 0, len(nsList.Items))
231 for _, ns := range nsList.Items {
232 if s.conf.EnableFilterNamespace && !namespace.CheckNamespace(context.TODO(), s.kubeCli, ns.Name, s.logger) {
233 continue
234 }
235 namespaces = append(namespaces, ns.Name)
236 }
237 } else {
238 namespaces = append(namespaces, s.conf.TargetNamespace)
239 }
240
241 sort.Sort(namespaces)
242 c.JSON(http.StatusOK, namespaces)
243 }
244
245
246
247
248
249
250
251
252 func (s *Service) getKinds(c *gin.Context) {
253 var kinds []string
254
255 allKinds := v1alpha1.AllKinds()
256 for name := range allKinds {
257 kinds = append(kinds, name)
258 }
259
260 sort.Strings(kinds)
261 c.JSON(http.StatusOK, kinds)
262 }
263
264
265
266
267
268
269
270
271
272 func (s *Service) getLabels(c *gin.Context) {
273 kubeCli, err := clientpool.ExtractTokenAndGetClient(c.Request.Header)
274 if err != nil {
275 _ = c.Error(u.ErrBadRequest.WrapWithNoMessage(err))
276 return
277 }
278
279 podNamespaceList := c.Query("podNamespaceList")
280
281 if len(podNamespaceList) == 0 {
282 c.Status(http.StatusInternalServerError)
283 _ = c.Error(u.ErrInternalServer.WrapWithNoMessage(errors.New("podNamespaceList is required")))
284 return
285 }
286
287 selector := v1alpha1.PodSelectorSpec{}
288 nsList := strings.Split(podNamespaceList, ",")
289 selector.Namespaces = nsList
290
291 ctx := context.TODO()
292 filteredPods, err := pod.SelectPods(ctx, kubeCli, nil, selector, s.conf.ClusterScoped, s.conf.TargetNamespace, s.conf.EnableFilterNamespace)
293 if err != nil {
294 c.Status(http.StatusInternalServerError)
295 _ = c.Error(u.ErrInternalServer.WrapWithNoMessage(err))
296 return
297 }
298
299 labels := make(u.MapStringSliceResponse)
300 for _, pod := range filteredPods {
301 for k, v := range pod.Labels {
302 if _, ok := labels[k]; ok {
303 if !inSlice(v, labels[k]) {
304 labels[k] = append(labels[k], v)
305 }
306 } else {
307 labels[k] = []string{v}
308 }
309 }
310 }
311
312 c.JSON(http.StatusOK, labels)
313 }
314
315
316
317
318
319
320
321
322
323 func (s *Service) getAnnotations(c *gin.Context) {
324 kubeCli, err := clientpool.ExtractTokenAndGetClient(c.Request.Header)
325 if err != nil {
326 _ = c.Error(u.ErrBadRequest.WrapWithNoMessage(err))
327 return
328 }
329
330 podNamespaceList := c.Query("podNamespaceList")
331
332 if len(podNamespaceList) == 0 {
333 c.Status(http.StatusInternalServerError)
334 _ = c.Error(u.ErrInternalServer.WrapWithNoMessage(errors.New("podNamespaceList is required")))
335 return
336 }
337
338 selector := v1alpha1.PodSelectorSpec{}
339 nsList := strings.Split(podNamespaceList, ",")
340 selector.Namespaces = nsList
341
342 ctx := context.TODO()
343 filteredPods, err := pod.SelectPods(ctx, kubeCli, nil, selector, s.conf.ClusterScoped, s.conf.TargetNamespace, s.conf.EnableFilterNamespace)
344 if err != nil {
345 c.Status(http.StatusInternalServerError)
346 _ = c.Error(u.ErrInternalServer.WrapWithNoMessage(err))
347 return
348 }
349
350 annotations := make(u.MapStringSliceResponse)
351 for _, pod := range filteredPods {
352 for k, v := range pod.Annotations {
353 if _, ok := annotations[k]; ok {
354 if !inSlice(v, annotations[k]) {
355 annotations[k] = append(annotations[k], v)
356 }
357 } else {
358 annotations[k] = []string{v}
359 }
360 }
361 }
362
363 c.JSON(http.StatusOK, annotations)
364 }
365
366
367
368
369
370
371
372
373 func (s *Service) getConfig(c *gin.Context) {
374 c.JSON(http.StatusOK, s.conf)
375 }
376
377
378
379
380
381
382
383
384
385
386 func (s *Service) getRbacConfig(c *gin.Context) {
387 namespace := c.Query("namespace")
388 roleType := c.Query("role")
389
390 var serviceAccount, role, roleBinding, verbs string
391 randomStr := randomStringWithCharset(5, charset)
392
393 scope := namespace
394 if len(namespace) == 0 {
395 namespace = "default"
396 scope = "cluster"
397 }
398 if roleType == roleManager {
399 verbs = `"get", "list", "watch", "create", "delete", "patch", "update"`
400 } else if roleType == roleViewer {
401 verbs = `"get", "list", "watch"`
402 } else {
403 c.Status(http.StatusBadRequest)
404 _ = c.Error(u.ErrBadRequest.WrapWithNoMessage(errors.New("roleType is neither manager nor viewer")))
405 return
406 }
407
408 serviceAccountName := fmt.Sprintf("account-%s-%s-%s", scope, roleType, randomStr)
409 roleName := fmt.Sprintf("role-%s-%s-%s", scope, roleType, randomStr)
410 roleBindingName := fmt.Sprintf("bind-%s-%s-%s", scope, roleType, randomStr)
411
412 serviceAccount = fmt.Sprintf(serviceAccountTemplate, namespace, serviceAccountName)
413 if scope == "cluster" {
414 role = fmt.Sprintf(clusterRoleTemplate, roleName, verbs)
415 roleBinding = fmt.Sprintf(clusterRoleBindingTemplate, roleBindingName, serviceAccountName, namespace, roleName)
416 } else {
417 role = fmt.Sprintf(roleTemplate, namespace, roleName, verbs)
418 roleBinding = fmt.Sprintf(roleBindingTemplate, roleBindingName, namespace, serviceAccountName, namespace, roleName)
419 }
420
421 rbacMap := make(map[string]string)
422 rbacMap[serviceAccountName] = serviceAccount + "\n---\n" + role + "\n---\n" + roleBinding
423
424 c.JSON(http.StatusOK, rbacMap)
425 }
426
427
428
429
430
431
432
433
434
435 func (s *Service) listPhysicalMachines(c *gin.Context) {
436 kubeCli, err := clientpool.ExtractTokenAndGetClient(c.Request.Header)
437 if err != nil {
438 u.SetAPIError(c, u.ErrBadRequest.WrapWithNoMessage(err))
439 return
440 }
441
442 selector := v1alpha1.PhysicalMachineSelectorSpec{}
443 if err := c.ShouldBindJSON(&selector); err != nil {
444 u.SetAPIError(c, u.ErrBadRequest.WrapWithNoMessage(err))
445 return
446 }
447 ctx := context.TODO()
448 filtered, err := physicalmachine.SelectPhysicalMachines(ctx, kubeCli, nil, selector, s.conf.ClusterScoped, s.conf.TargetNamespace, s.conf.EnableFilterNamespace, s.logger)
449 if err != nil {
450 u.SetAPIError(c, u.ErrInternalServer.WrapWithNoMessage(err))
451 return
452 }
453
454 physicalMachines := make([]apiservertypes.PhysicalMachine, 0, len(filtered))
455 for _, pm := range filtered {
456 physicalMachines = append(physicalMachines, apiservertypes.PhysicalMachine{
457 Name: pm.Name,
458 Namespace: pm.Namespace,
459 Address: pm.Spec.Address,
460 })
461 }
462
463 c.JSON(http.StatusOK, physicalMachines)
464 }
465
466
467
468
469
470
471
472
473
474 func (s *Service) getPhysicalMachineLabels(c *gin.Context) {
475
476 kubeCli, err := clientpool.ExtractTokenAndGetClient(c.Request.Header)
477 if err != nil {
478 u.SetAPIError(c, u.ErrBadRequest.WrapWithNoMessage(err))
479 return
480 }
481
482 physicalMachineNamespaceList := c.Query("physicalMachineNamespaceList")
483
484 if len(physicalMachineNamespaceList) == 0 {
485 u.SetAPIError(c, u.ErrInternalServer.WrapWithNoMessage(errors.New("physicalMachineNamespaceList is required")))
486 return
487 }
488
489 selector := v1alpha1.PhysicalMachineSelectorSpec{}
490 nsList := strings.Split(physicalMachineNamespaceList, ",")
491 selector.Namespaces = nsList
492
493 ctx := context.TODO()
494 filtered, err := physicalmachine.SelectPhysicalMachines(ctx, kubeCli, nil, selector, s.conf.ClusterScoped, s.conf.TargetNamespace, s.conf.EnableFilterNamespace, s.logger)
495 if err != nil {
496 u.SetAPIError(c, u.ErrInternalServer.WrapWithNoMessage(err))
497 return
498 }
499
500 labels := make(u.MapStringSliceResponse)
501 for _, obj := range filtered {
502 for k, v := range obj.Labels {
503 if _, ok := labels[k]; ok {
504 if !inSlice(v, labels[k]) {
505 labels[k] = append(labels[k], v)
506 }
507 } else {
508 labels[k] = []string{v}
509 }
510 }
511 }
512
513 c.JSON(http.StatusOK, labels)
514 }
515
516
517
518
519
520
521
522
523
524 func (s *Service) getPhysicalMachineAnnotations(c *gin.Context) {
525
526 kubeCli, err := clientpool.ExtractTokenAndGetClient(c.Request.Header)
527 if err != nil {
528 u.SetAPIError(c, u.ErrBadRequest.WrapWithNoMessage(err))
529 return
530 }
531
532 physicalMachineNamespaceList := c.Query("physicalMachineNamespaceList")
533
534 if len(physicalMachineNamespaceList) == 0 {
535 u.SetAPIError(c, u.ErrInternalServer.WrapWithNoMessage(errors.New("physicalMachineNamespaceList is required")))
536 return
537 }
538 selector := v1alpha1.PhysicalMachineSelectorSpec{}
539 nsList := strings.Split(physicalMachineNamespaceList, ",")
540 selector.Namespaces = nsList
541
542 ctx := context.TODO()
543 filtered, err := physicalmachine.SelectPhysicalMachines(ctx, kubeCli, nil, selector, s.conf.ClusterScoped, s.conf.TargetNamespace, s.conf.EnableFilterNamespace, s.logger)
544 if err != nil {
545 u.SetAPIError(c, u.ErrInternalServer.WrapWithNoMessage(err))
546 return
547 }
548
549 annotations := make(u.MapStringSliceResponse)
550 for _, obj := range filtered {
551 for k, v := range obj.Annotations {
552 if _, ok := annotations[k]; ok {
553 if !inSlice(v, annotations[k]) {
554 annotations[k] = append(annotations[k], v)
555 }
556 } else {
557 annotations[k] = []string{v}
558 }
559 }
560 }
561
562 c.JSON(http.StatusOK, annotations)
563 }
564
565
566 func inSlice(v string, sl []string) bool {
567 for _, vv := range sl {
568 if vv == v {
569 return true
570 }
571 }
572 return false
573 }
574
575 const charset = "abcdefghijklmnopqrstuvwxyz"
576
577 func randomStringWithCharset(length int, charset string) string {
578 b := make([]byte, length)
579 for i := range b {
580 num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
581 b[i] = charset[num.Int64()]
582 }
583 return string(b)
584 }
585