...

Source file src/github.com/chaos-mesh/chaos-mesh/pkg/dashboard/apiserver/template/template.go

Documentation: github.com/chaos-mesh/chaos-mesh/pkg/dashboard/apiserver/template

     1  // Copyright Chaos Mesh Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  // http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  
    16  package template
    17  
    18  import (
    19  	"context"
    20  	"net/http"
    21  	"sort"
    22  	"time"
    23  
    24  	"github.com/gin-gonic/gin"
    25  	"github.com/go-logr/logr"
    26  	"gopkg.in/yaml.v2"
    27  	v1 "k8s.io/api/core/v1"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/types"
    30  	"sigs.k8s.io/controller-runtime/pkg/client"
    31  
    32  	"github.com/chaos-mesh/chaos-mesh/api/v1alpha1"
    33  	"github.com/chaos-mesh/chaos-mesh/pkg/clientpool"
    34  	config "github.com/chaos-mesh/chaos-mesh/pkg/config"
    35  	apiservertypes "github.com/chaos-mesh/chaos-mesh/pkg/dashboard/apiserver/types"
    36  	u "github.com/chaos-mesh/chaos-mesh/pkg/dashboard/apiserver/utils"
    37  )
    38  
    39  // Service defines a handler service for cluster common objects.
    40  type Service struct {
    41  	conf   *config.ChaosDashboardConfig
    42  	logger logr.Logger
    43  }
    44  
    45  func NewService(conf *config.ChaosDashboardConfig, logger logr.Logger) *Service {
    46  	return &Service{conf: conf, logger: logger}
    47  }
    48  
    49  func Register(r *gin.RouterGroup, s *Service) {
    50  	endpoint := r.Group("/templates")
    51  
    52  	statusCheckEndpoint := endpoint.Group("/statuschecks")
    53  	statusCheckEndpoint.GET("", s.listStatusCheckTemplate)
    54  	statusCheckEndpoint.POST("", s.createStatusCheckTemplate)
    55  	statusCheckEndpoint.GET("/statuscheck", s.getStatusCheckTemplateDetail)
    56  	statusCheckEndpoint.PUT("/statuscheck", s.updateStatusCheckTemplate)
    57  	statusCheckEndpoint.DELETE("/statuscheck", s.deleteStatusCheckTemplate)
    58  }
    59  
    60  // @Summary List status check templates.
    61  // @Description Get status check templates from k8s cluster in real time.
    62  // @Tags template
    63  // @Produce json
    64  // @Param namespace query string false "filter status check templates by namespace"
    65  // @Param name query string false "filter status check templates by name"
    66  // @Success 200 {array} apiservertypes.StatusCheckTemplateBase
    67  // @Failure 400 {object} u.APIError
    68  // @Failure 500 {object} u.APIError
    69  // @Router /templates/statuschecks [get]
    70  func (s *Service) listStatusCheckTemplate(c *gin.Context) {
    71  	kubeCli, err := clientpool.ExtractTokenAndGetClient(c.Request.Header)
    72  	if err != nil {
    73  		u.SetAPIError(c, u.ErrBadRequest.WrapWithNoMessage(err))
    74  		return
    75  	}
    76  
    77  	ns, name := c.Query("namespace"), c.Query("name")
    78  	if ns == "" && !s.conf.ClusterScoped && s.conf.TargetNamespace != "" {
    79  		ns = s.conf.TargetNamespace
    80  		s.logger.Info("Replace query namespace", "ns", ns)
    81  	}
    82  
    83  	configMapList := v1.ConfigMapList{}
    84  	if err = kubeCli.List(context.Background(), &configMapList,
    85  		client.InNamespace(ns),
    86  		client.MatchingLabels{
    87  			v1alpha1.TemplateTypeLabelKey: v1alpha1.KindStatusCheck,
    88  			v1alpha1.ManagedByLabelKey:    v1alpha1.ManagedByLabelValue,
    89  		},
    90  	); err != nil {
    91  		u.SetAPImachineryError(c, err)
    92  		return
    93  	}
    94  
    95  	templates := make([]*apiservertypes.StatusCheckTemplateBase, 0)
    96  	for _, cm := range configMapList.Items {
    97  		templateName := v1alpha1.GetTemplateName(cm)
    98  		if templateName == "" {
    99  			// skip illegal template
   100  			continue
   101  		}
   102  		if name != "" && templateName != name {
   103  			continue
   104  		}
   105  
   106  		templates = append(templates, &apiservertypes.StatusCheckTemplateBase{
   107  			Namespace:   cm.Namespace,
   108  			Name:        templateName,
   109  			UID:         string(cm.UID),
   110  			Created:     cm.CreationTimestamp.Format(time.RFC3339),
   111  			Description: v1alpha1.GetTemplateDescription(cm),
   112  		})
   113  	}
   114  
   115  	sort.Slice(templates, func(i, j int) bool {
   116  		return templates[i].Created > templates[j].Created
   117  	})
   118  
   119  	c.JSON(http.StatusOK, templates)
   120  }
   121  
   122  // @Summary Create a new status check template.
   123  // @Description Pass a JSON object to create a new status check template.
   124  // @Tags templates
   125  // @Accept json
   126  // @Produce json
   127  // @Param statuscheck body apiservertypes.StatusCheckTemplate true "the status check definition"
   128  // @Success 200 {object} apiservertypes.StatusCheckTemplate
   129  // @Failure 400 {object} u.APIError
   130  // @Failure 500 {object} u.APIError
   131  // @Router /templates/statuschecks [post]
   132  func (s *Service) createStatusCheckTemplate(c *gin.Context) {
   133  	kubeCli, err := clientpool.ExtractTokenAndGetClient(c.Request.Header)
   134  	if err != nil {
   135  		u.SetAPIError(c, u.ErrBadRequest.WrapWithNoMessage(err))
   136  		return
   137  	}
   138  
   139  	var template apiservertypes.StatusCheckTemplate
   140  	if err = u.ShouldBindBodyWithJSON(c, &template); err != nil {
   141  		return
   142  	}
   143  	template.Spec.Default()
   144  	if _, err := template.Spec.Validate(); err != nil {
   145  		u.SetAPIError(c, u.ErrInternalServer.WrapWithNoMessage(err))
   146  		return
   147  	}
   148  
   149  	spec, err := yaml.Marshal(template.Spec)
   150  	if err != nil {
   151  		u.SetAPIError(c, u.ErrInternalServer.WrapWithNoMessage(err))
   152  		return
   153  	}
   154  	cm := v1.ConfigMap{
   155  		ObjectMeta: metav1.ObjectMeta{
   156  			Namespace: template.Namespace,
   157  			Name:      v1alpha1.GenerateTemplateName(template.Name),
   158  			Labels: map[string]string{
   159  				v1alpha1.TemplateTypeLabelKey: v1alpha1.KindStatusCheck,
   160  				v1alpha1.ManagedByLabelKey:    v1alpha1.ManagedByLabelValue,
   161  			},
   162  			Annotations: map[string]string{
   163  				v1alpha1.TemplateNameAnnotationKey:        template.Name,
   164  				v1alpha1.TemplateDescriptionAnnotationKey: template.Description,
   165  			},
   166  		},
   167  		Data: map[string]string{v1alpha1.StatusCheckTemplateKey: string(spec)},
   168  	}
   169  	if err = kubeCli.Create(context.Background(), &cm); err != nil {
   170  		u.SetAPImachineryError(c, err)
   171  		return
   172  	}
   173  
   174  	c.JSON(http.StatusOK, template)
   175  }
   176  
   177  // @Summary Get a status check template.
   178  // @Description Get the status check template's detail by namespaced name.
   179  // @Tags templates
   180  // @Produce json
   181  // @Param namespace query string true "the namespace of status check templates"
   182  // @Param name query string true "the name of status check templates"
   183  // @Success 200 {object} apiservertypes.StatusCheckTemplateDetail
   184  // @Failure 400 {object} u.APIError
   185  // @Failure 404 {object} u.APIError
   186  // @Failure 500 {object} u.APIError
   187  // @Router /templates/statuschecks/statuscheck [get]
   188  func (s *Service) getStatusCheckTemplateDetail(c *gin.Context) {
   189  	kubeCli, err := clientpool.ExtractTokenAndGetClient(c.Request.Header)
   190  	if err != nil {
   191  		u.SetAPIError(c, u.ErrBadRequest.WrapWithNoMessage(err))
   192  		return
   193  	}
   194  
   195  	ns, name := c.Query("namespace"), c.Query("name")
   196  	if ns == "" && !s.conf.ClusterScoped && s.conf.TargetNamespace != "" {
   197  		ns = s.conf.TargetNamespace
   198  		s.logger.Info("Replace query namespace", "ns", ns)
   199  	}
   200  	if name == "" {
   201  		u.SetAPIError(c, u.ErrBadRequest.New("name is required"))
   202  		return
   203  	}
   204  
   205  	var cm v1.ConfigMap
   206  	if err = kubeCli.Get(context.Background(),
   207  		types.NamespacedName{
   208  			Namespace: ns,
   209  			Name:      v1alpha1.GenerateTemplateName(name),
   210  		}, &cm); err != nil {
   211  		u.SetAPImachineryError(c, err)
   212  		return
   213  	}
   214  
   215  	if !v1alpha1.IsStatusCheckTemplate(cm) {
   216  		u.SetAPIError(c, u.ErrInternalServer.New("invalid status check template"))
   217  		return
   218  	}
   219  
   220  	var spec v1alpha1.StatusCheckTemplate
   221  	if err := yaml.Unmarshal([]byte(cm.Data[v1alpha1.StatusCheckTemplateKey]), &spec); err != nil {
   222  		u.SetAPIError(c, u.ErrInternalServer.WrapWithNoMessage(err))
   223  		return
   224  	}
   225  	detail := apiservertypes.StatusCheckTemplateDetail{
   226  		StatusCheckTemplateBase: apiservertypes.StatusCheckTemplateBase{
   227  			Namespace:   cm.Namespace,
   228  			Name:        v1alpha1.GetTemplateName(cm),
   229  			UID:         string(cm.UID),
   230  			Created:     cm.CreationTimestamp.Format(time.RFC3339),
   231  			Description: v1alpha1.GetTemplateDescription(cm),
   232  		},
   233  		Spec: spec,
   234  	}
   235  	c.JSON(http.StatusOK, detail)
   236  }
   237  
   238  // @Summary Update a status check template.
   239  // @Description Update a status check template by namespaced name.
   240  // @Tags templates
   241  // @Produce json
   242  // @Param request body apiservertypes.StatusCheckTemplate true "Request body"
   243  // @Success 200 {object} apiservertypes.StatusCheckTemplate
   244  // @Failure 400 {object} u.APIError
   245  // @Failure 500 {object} u.APIError
   246  // @Router /templates/statuschecks/statuscheck [put]
   247  func (s *Service) updateStatusCheckTemplate(c *gin.Context) {
   248  	kubeCli, err := clientpool.ExtractTokenAndGetClient(c.Request.Header)
   249  	if err != nil {
   250  		u.SetAPIError(c, u.ErrBadRequest.WrapWithNoMessage(err))
   251  		return
   252  	}
   253  
   254  	var template apiservertypes.StatusCheckTemplate
   255  	if err = u.ShouldBindBodyWithJSON(c, &template); err != nil {
   256  		return
   257  	}
   258  	template.Spec.Default()
   259  	if _, err := template.Spec.Validate(); err != nil {
   260  		u.SetAPIError(c, u.ErrInternalServer.WrapWithNoMessage(err))
   261  		return
   262  	}
   263  
   264  	var cm v1.ConfigMap
   265  	if err = kubeCli.Get(context.Background(),
   266  		types.NamespacedName{
   267  			Namespace: template.Namespace,
   268  			Name:      v1alpha1.GenerateTemplateName(template.Name),
   269  		}, &cm); err != nil {
   270  		u.SetAPImachineryError(c, err)
   271  		return
   272  	}
   273  	if !v1alpha1.IsStatusCheckTemplate(cm) {
   274  		u.SetAPIError(c, u.ErrInternalServer.New("invalid status check template"))
   275  		return
   276  	}
   277  
   278  	spec, err := yaml.Marshal(template.Spec)
   279  	if err != nil {
   280  		u.SetAPIError(c, u.ErrInternalServer.WrapWithNoMessage(err))
   281  		return
   282  	}
   283  	if cm.Data == nil {
   284  		cm.Data = map[string]string{v1alpha1.StatusCheckTemplateKey: string(spec)}
   285  	} else {
   286  		cm.Data[v1alpha1.StatusCheckTemplateKey] = string(spec)
   287  	}
   288  	cm.Annotations[v1alpha1.TemplateDescriptionAnnotationKey] = template.Description
   289  
   290  	if err := kubeCli.Update(context.Background(), &cm); err != nil {
   291  		u.SetAPImachineryError(c, err)
   292  		return
   293  	}
   294  	c.JSON(http.StatusOK, template)
   295  }
   296  
   297  // @Summary Delete a status check template.
   298  // @Description Delete the status check template by namespaced name.
   299  // @Tags templates
   300  // @Produce json
   301  // @Param namespace query string true "the namespace of status check templates"
   302  // @Param name query string true "the name of status check templates"
   303  // @Success 200 {object} u.Response
   304  // @Failure 400 {object} u.APIError
   305  // @Failure 404 {object} u.APIError
   306  // @Failure 500 {object} u.APIError
   307  // @Router /templates/statuschecks/statuscheck [delete]
   308  func (s *Service) deleteStatusCheckTemplate(c *gin.Context) {
   309  	kubeCli, err := clientpool.ExtractTokenAndGetClient(c.Request.Header)
   310  	if err != nil {
   311  		u.SetAPIError(c, u.ErrBadRequest.WrapWithNoMessage(err))
   312  		return
   313  	}
   314  
   315  	ns, name := c.Query("namespace"), c.Query("name")
   316  	if ns == "" && !s.conf.ClusterScoped && s.conf.TargetNamespace != "" {
   317  		ns = s.conf.TargetNamespace
   318  		s.logger.Info("Replace query namespace", "ns", ns)
   319  	}
   320  	if name == "" {
   321  		u.SetAPIError(c, u.ErrBadRequest.New("name is required"))
   322  		return
   323  	}
   324  
   325  	var cm v1.ConfigMap
   326  	if err = kubeCli.Get(context.Background(),
   327  		types.NamespacedName{
   328  			Namespace: ns,
   329  			Name:      v1alpha1.GenerateTemplateName(name),
   330  		}, &cm); err != nil {
   331  		u.SetAPImachineryError(c, err)
   332  		return
   333  	}
   334  	if !v1alpha1.IsStatusCheckTemplate(cm) {
   335  		u.SetAPIError(c, u.ErrInternalServer.New("invalid status check template"))
   336  		return
   337  	}
   338  
   339  	if err := kubeCli.Delete(context.Background(), &cm); err != nil {
   340  		u.SetAPImachineryError(c, err)
   341  		return
   342  	}
   343  	c.JSON(http.StatusOK, u.ResponseSuccess)
   344  }
   345