...

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

Documentation: github.com/chaos-mesh/chaos-mesh/pkg/apiserver/archive

     1  // Copyright 2020 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  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package archive
    15  
    16  import (
    17  	"context"
    18  	"encoding/json"
    19  	"fmt"
    20  	"net/http"
    21  	"strings"
    22  	"time"
    23  
    24  	"github.com/gin-gonic/gin"
    25  	"github.com/jinzhu/gorm"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	ctrl "sigs.k8s.io/controller-runtime"
    28  
    29  	"github.com/chaos-mesh/chaos-mesh/api/v1alpha1"
    30  	"github.com/chaos-mesh/chaos-mesh/pkg/apiserver/utils"
    31  	config "github.com/chaos-mesh/chaos-mesh/pkg/config/dashboard"
    32  	"github.com/chaos-mesh/chaos-mesh/pkg/core"
    33  )
    34  
    35  var log = ctrl.Log.WithName("archive api")
    36  
    37  // Service defines a handler service for archive experiments.
    38  type Service struct {
    39  	archive         core.ExperimentStore
    40  	archiveSchedule core.ScheduleStore
    41  	event           core.EventStore
    42  	workflowStore   core.WorkflowStore
    43  	conf            *config.ChaosDashboardConfig
    44  }
    45  
    46  // NewService returns an archive experiment service instance.
    47  func NewService(
    48  	archive core.ExperimentStore,
    49  	archiveSchedule core.ScheduleStore,
    50  	event core.EventStore,
    51  	workflowStore core.WorkflowStore,
    52  	conf *config.ChaosDashboardConfig,
    53  ) *Service {
    54  	return &Service{
    55  		archive:         archive,
    56  		archiveSchedule: archiveSchedule,
    57  		event:           event,
    58  		workflowStore:   workflowStore,
    59  		conf:            conf,
    60  	}
    61  }
    62  
    63  // StatusResponse defines a common status struct.
    64  type StatusResponse struct {
    65  	Status string `json:"status"`
    66  }
    67  
    68  // Register mounts our HTTP handler on the mux.
    69  func Register(r *gin.RouterGroup, s *Service) {
    70  	endpoint := r.Group("/archives")
    71  	endpoint.Use(func(c *gin.Context) {
    72  		utils.AuthRequired(c, s.conf.ClusterScoped, s.conf.TargetNamespace)
    73  	})
    74  
    75  	endpoint.GET("", s.list)
    76  	endpoint.GET("/detail", s.detail)
    77  	endpoint.DELETE("/:uid", s.delete)
    78  	endpoint.DELETE("/", s.batchDelete)
    79  
    80  	endpoint.GET("/schedules", s.listSchedule)
    81  	endpoint.GET("/schedules/:uid", s.detailSchedule)
    82  	endpoint.DELETE("/schedules/:uid", s.deleteSchedule)
    83  	endpoint.DELETE("/schedules", s.batchDeleteSchedule)
    84  
    85  	endpoint.GET("/workflows", s.listWorkflow)
    86  	endpoint.GET("/workflows/:uid", s.detailWorkflow)
    87  	endpoint.DELETE("/workflows/:uid", s.deleteWorkflow)
    88  	endpoint.DELETE("/workflows", s.batchDeleteWorkflow)
    89  }
    90  
    91  // Archive defines the basic information of an archive.
    92  type Archive struct {
    93  	UID       string    `json:"uid"`
    94  	Kind      string    `json:"kind"`
    95  	Namespace string    `json:"namespace"`
    96  	Name      string    `json:"name"`
    97  	CreatedAt time.Time `json:"created_at"`
    98  }
    99  
   100  // Detail represents an archive instance.
   101  type Detail struct {
   102  	Archive
   103  	KubeObject core.KubeObjectDesc `json:"kube_object"`
   104  }
   105  
   106  // @Summary Get archived chaos experiments.
   107  // @Description Get archived chaos experiments.
   108  // @Tags archives
   109  // @Produce json
   110  // @Param namespace query string false "namespace"
   111  // @Param name query string false "name"
   112  // @Param kind query string false "kind" Enums(PodChaos, IOChaos, NetworkChaos, TimeChaos, KernelChaos, StressChaos)
   113  // @Success 200 {array} Archive
   114  // @Router /archives [get]
   115  // @Failure 500 {object} utils.APIError
   116  func (s *Service) list(c *gin.Context) {
   117  	kind := c.Query("kind")
   118  	name := c.Query("name")
   119  	ns := c.Query("namespace")
   120  	if len(ns) == 0 && !s.conf.ClusterScoped &&
   121  		len(s.conf.TargetNamespace) != 0 {
   122  		ns = s.conf.TargetNamespace
   123  	}
   124  
   125  	metas, err := s.archive.ListMeta(context.Background(), kind, ns, name, true)
   126  	if err != nil {
   127  		c.Status(http.StatusInternalServerError)
   128  		_ = c.Error(utils.ErrInternalServer.NewWithNoMessage())
   129  		return
   130  	}
   131  
   132  	archives := make([]Archive, 0)
   133  
   134  	for _, meta := range metas {
   135  		archives = append(archives, Archive{
   136  			UID:       meta.UID,
   137  			Kind:      meta.Kind,
   138  			Namespace: meta.Namespace,
   139  			Name:      meta.Name,
   140  			CreatedAt: meta.StartTime,
   141  		})
   142  	}
   143  
   144  	c.JSON(http.StatusOK, archives)
   145  }
   146  
   147  // @Summary Get the detail of an archived chaos experiment.
   148  // @Description Get the detail of an archived chaos experiment.
   149  // @Tags archives
   150  // @Produce json
   151  // @Param uid query string true "uid"
   152  // @Success 200 {object} Detail
   153  // @Router /archives/detail [get]
   154  // @Failure 500 {object} utils.APIError
   155  func (s *Service) detail(c *gin.Context) {
   156  	var (
   157  		err        error
   158  		kubeObject core.KubeObjectDesc
   159  		detail     Detail
   160  	)
   161  	uid := c.Query("uid")
   162  	namespace := c.Query("namespace")
   163  	if len(namespace) == 0 && !s.conf.ClusterScoped &&
   164  		len(s.conf.TargetNamespace) != 0 {
   165  		namespace = s.conf.TargetNamespace
   166  	}
   167  
   168  	if uid == "" {
   169  		c.Status(http.StatusBadRequest)
   170  		_ = c.Error(utils.ErrInvalidRequest.New("uid cannot be empty"))
   171  		return
   172  	}
   173  
   174  	exp, err := s.archive.FindByUID(context.Background(), uid)
   175  	if err != nil {
   176  		if gorm.IsRecordNotFoundError(err) {
   177  			c.Status(http.StatusInternalServerError)
   178  			_ = c.Error(utils.ErrInvalidRequest.New("the archive is not found"))
   179  		} else {
   180  			c.Status(http.StatusInternalServerError)
   181  			_ = c.Error(utils.ErrInternalServer.NewWithNoMessage())
   182  		}
   183  		return
   184  	}
   185  
   186  	if len(namespace) != 0 && exp.Namespace != namespace {
   187  		c.Status(http.StatusBadRequest)
   188  		_ = c.Error(utils.ErrInvalidRequest.New("exp %s belong to namespace %s but not namespace %s", uid, exp.Namespace, namespace))
   189  		return
   190  	}
   191  
   192  	switch exp.Kind {
   193  	case v1alpha1.KindPodChaos:
   194  		kubeObject, err = exp.ParsePodChaos()
   195  	case v1alpha1.KindIOChaos:
   196  		kubeObject, err = exp.ParseIOChaos()
   197  	case v1alpha1.KindNetworkChaos:
   198  		kubeObject, err = exp.ParseNetworkChaos()
   199  	case v1alpha1.KindTimeChaos:
   200  		kubeObject, err = exp.ParseTimeChaos()
   201  	case v1alpha1.KindKernelChaos:
   202  		kubeObject, err = exp.ParseKernelChaos()
   203  	case v1alpha1.KindStressChaos:
   204  		kubeObject, err = exp.ParseStressChaos()
   205  	case v1alpha1.KindDNSChaos:
   206  		kubeObject, err = exp.ParseDNSChaos()
   207  	case v1alpha1.KindAWSChaos:
   208  		kubeObject, err = exp.ParseAWSChaos()
   209  	case v1alpha1.KindGCPChaos:
   210  		kubeObject, err = exp.ParseGCPChaos()
   211  	default:
   212  		err = fmt.Errorf("kind %s is not support", exp.Kind)
   213  	}
   214  	if err != nil {
   215  		c.Status(http.StatusInternalServerError)
   216  		_ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
   217  		return
   218  	}
   219  
   220  	detail = Detail{
   221  		Archive: Archive{
   222  			UID:       exp.UID,
   223  			Kind:      exp.Kind,
   224  			Name:      exp.Name,
   225  			Namespace: exp.Namespace,
   226  			CreatedAt: exp.StartTime,
   227  		},
   228  		KubeObject: kubeObject,
   229  	}
   230  
   231  	c.JSON(http.StatusOK, detail)
   232  }
   233  
   234  // @Summary Delete the specified archived experiment.
   235  // @Description Delete the specified archived experiment.
   236  // @Tags archives
   237  // @Produce json
   238  // @Param uid path string true "uid"
   239  // @Success 200 {object} StatusResponse
   240  // @Failure 500 {object} utils.APIError
   241  // @Router /archives/{uid} [delete]
   242  func (s *Service) delete(c *gin.Context) {
   243  	var (
   244  		err error
   245  		exp *core.Experiment
   246  	)
   247  
   248  	uid := c.Param("uid")
   249  
   250  	if exp, err = s.archive.FindByUID(context.Background(), uid); err != nil {
   251  		if gorm.IsRecordNotFoundError(err) {
   252  			c.Status(http.StatusInternalServerError)
   253  			_ = c.Error(utils.ErrInvalidRequest.New("the archived experiment is not found"))
   254  		} else {
   255  			c.Status(http.StatusInternalServerError)
   256  			_ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
   257  		}
   258  		return
   259  	}
   260  
   261  	if err = s.archive.Delete(context.Background(), exp); err != nil {
   262  		c.Status(http.StatusInternalServerError)
   263  		_ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
   264  	} else {
   265  		if err = s.event.DeleteByUID(context.Background(), uid); err != nil {
   266  			c.Status(http.StatusInternalServerError)
   267  			_ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
   268  		} else {
   269  			c.JSON(http.StatusOK, StatusResponse{Status: "success"})
   270  		}
   271  	}
   272  }
   273  
   274  // @Summary Delete the specified archived experiment.
   275  // @Description Delete the specified archived experiment.
   276  // @Tags archives
   277  // @Produce json
   278  // @Param uids query string true "uids"
   279  // @Success 200 {object} StatusResponse
   280  // @Failure 500 {object} utils.APIError
   281  // @Router /archives [delete]
   282  func (s *Service) batchDelete(c *gin.Context) {
   283  	var (
   284  		err      error
   285  		uidSlice []string
   286  	)
   287  
   288  	uids := c.Query("uids")
   289  	if uids == "" {
   290  		c.Status(http.StatusBadRequest)
   291  		_ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(fmt.Errorf("uids cannot be empty")))
   292  		return
   293  	}
   294  	uidSlice = strings.Split(uids, ",")
   295  
   296  	if err = s.archive.DeleteByUIDs(context.Background(), uidSlice); err != nil {
   297  		_ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
   298  		c.Status(http.StatusInternalServerError)
   299  		return
   300  	}
   301  	if err = s.event.DeleteByUIDs(context.Background(), uidSlice); err != nil {
   302  		_ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
   303  		c.Status(http.StatusInternalServerError)
   304  		return
   305  	}
   306  
   307  	c.JSON(http.StatusOK, StatusResponse{Status: "success"})
   308  }
   309  
   310  // @Summary Get archived schedule experiments.
   311  // @Description Get archived schedule experiments.
   312  // @Tags archives
   313  // @Produce json
   314  // @Param namespace query string false "namespace"
   315  // @Param name query string false "name"
   316  // @Success 200 {array} Archive
   317  // @Router /archives/schedules [get]
   318  // @Failure 500 {object} utils.APIError
   319  func (s *Service) listSchedule(c *gin.Context) {
   320  	name := c.Query("name")
   321  	ns := c.Query("namespace")
   322  
   323  	metas, err := s.archiveSchedule.ListMeta(context.Background(), ns, name, true)
   324  	if err != nil {
   325  		c.Status(http.StatusInternalServerError)
   326  		_ = c.Error(utils.ErrInternalServer.NewWithNoMessage())
   327  		return
   328  	}
   329  
   330  	archives := make([]Archive, 0)
   331  
   332  	for _, meta := range metas {
   333  		archives = append(archives, Archive{
   334  			UID:       meta.UID,
   335  			Kind:      meta.Kind,
   336  			Namespace: meta.Namespace,
   337  			Name:      meta.Name,
   338  			CreatedAt: meta.StartTime,
   339  		})
   340  	}
   341  
   342  	c.JSON(http.StatusOK, archives)
   343  }
   344  
   345  // @Summary Get the detail of an archived schedule experiment.
   346  // @Description Get the detail of an archived schedule experiment.
   347  // @Tags archives
   348  // @Produce json
   349  // @Param uid query string true "uid"
   350  // @Success 200 {object} Detail
   351  // @Router /archives/schedules/{uid} [get]
   352  // @Failure 500 {object} utils.APIError
   353  func (s *Service) detailSchedule(c *gin.Context) {
   354  	var (
   355  		err    error
   356  		detail Detail
   357  	)
   358  	uid := c.Param("uid")
   359  
   360  	if uid == "" {
   361  		c.Status(http.StatusBadRequest)
   362  		_ = c.Error(utils.ErrInvalidRequest.New("uid cannot be empty"))
   363  		return
   364  	}
   365  
   366  	exp, err := s.archiveSchedule.FindByUID(context.Background(), uid)
   367  	if err != nil {
   368  		if gorm.IsRecordNotFoundError(err) {
   369  			c.Status(http.StatusInternalServerError)
   370  			_ = c.Error(utils.ErrInvalidRequest.New("the archive schedule is not found"))
   371  		} else {
   372  			c.Status(http.StatusInternalServerError)
   373  			_ = c.Error(utils.ErrInternalServer.NewWithNoMessage())
   374  		}
   375  		return
   376  	}
   377  
   378  	sch := &v1alpha1.Schedule{}
   379  	if err := json.Unmarshal([]byte(exp.Schedule), &sch); err != nil {
   380  		c.Status(http.StatusInternalServerError)
   381  		_ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
   382  		return
   383  	}
   384  
   385  	detail = Detail{
   386  		Archive: Archive{
   387  			UID:       exp.UID,
   388  			Kind:      exp.Kind,
   389  			Name:      exp.Name,
   390  			Namespace: exp.Namespace,
   391  			CreatedAt: exp.StartTime,
   392  		},
   393  		KubeObject: core.KubeObjectDesc{
   394  			TypeMeta: metav1.TypeMeta{
   395  				APIVersion: sch.APIVersion,
   396  				Kind:       sch.Kind,
   397  			},
   398  			Meta: core.KubeObjectMeta{
   399  				Name:        sch.Name,
   400  				Namespace:   sch.Namespace,
   401  				Labels:      sch.Labels,
   402  				Annotations: sch.Annotations,
   403  			},
   404  			Spec: sch.Spec,
   405  		},
   406  	}
   407  
   408  	c.JSON(http.StatusOK, detail)
   409  }
   410  
   411  // @Summary Delete the specified archived schedule.
   412  // @Description Delete the specified archived schedule.
   413  // @Tags archives
   414  // @Produce json
   415  // @Param uid path string true "uid"
   416  // @Success 200 {object} StatusResponse
   417  // @Failure 500 {object} utils.APIError
   418  // @Router /archives/schedules/{uid} [delete]
   419  func (s *Service) deleteSchedule(c *gin.Context) {
   420  	var (
   421  		err error
   422  		exp *core.Schedule
   423  	)
   424  
   425  	uid := c.Param("uid")
   426  
   427  	if exp, err = s.archiveSchedule.FindByUID(context.Background(), uid); err != nil {
   428  		if gorm.IsRecordNotFoundError(err) {
   429  			c.Status(http.StatusInternalServerError)
   430  			_ = c.Error(utils.ErrInvalidRequest.New("the archived schedule is not found"))
   431  		} else {
   432  			c.Status(http.StatusInternalServerError)
   433  			_ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
   434  		}
   435  		return
   436  	}
   437  
   438  	if err = s.archiveSchedule.Delete(context.Background(), exp); err != nil {
   439  		c.Status(http.StatusInternalServerError)
   440  		_ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
   441  	} else {
   442  		if err = s.event.DeleteByUID(context.Background(), uid); err != nil {
   443  			c.Status(http.StatusInternalServerError)
   444  			_ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
   445  		} else {
   446  			c.JSON(http.StatusOK, StatusResponse{Status: "success"})
   447  		}
   448  	}
   449  }
   450  
   451  // @Summary Delete the specified archived schedule.
   452  // @Description Delete the specified archived schedule.
   453  // @Tags archives
   454  // @Produce json
   455  // @Param uids query string true "uids"
   456  // @Success 200 {object} StatusResponse
   457  // @Failure 500 {object} utils.APIError
   458  // @Router /archives/schedules [delete]
   459  func (s *Service) batchDeleteSchedule(c *gin.Context) {
   460  	var (
   461  		err      error
   462  		uidSlice []string
   463  	)
   464  
   465  	uids := c.Query("uids")
   466  	if uids == "" {
   467  		c.Status(http.StatusBadRequest)
   468  		_ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(fmt.Errorf("uids cannot be empty")))
   469  		return
   470  	}
   471  	uidSlice = strings.Split(uids, ",")
   472  
   473  	if err = s.archiveSchedule.DeleteByUIDs(context.Background(), uidSlice); err != nil {
   474  		_ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
   475  		c.Status(http.StatusInternalServerError)
   476  		return
   477  	}
   478  	if err = s.event.DeleteByUIDs(context.Background(), uidSlice); err != nil {
   479  		_ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
   480  		c.Status(http.StatusInternalServerError)
   481  		return
   482  	}
   483  
   484  	c.JSON(http.StatusOK, StatusResponse{Status: "success"})
   485  }
   486  
   487  // @Summary Get archived workflow.
   488  // @Description Get archived workflow.
   489  // @Tags archives
   490  // @Produce json
   491  // @Param namespace query string false "namespace"
   492  // @Param name query string false "name"
   493  // @Success 200 {array} Archive
   494  // @Router /archives/workflows [get]
   495  // @Failure 500 {object} utils.APIError
   496  func (s *Service) listWorkflow(c *gin.Context) {
   497  	name := c.Query("name")
   498  	ns := c.Query("namespace")
   499  
   500  	metas, err := s.workflowStore.ListMeta(context.Background(), ns, name, true)
   501  	if err != nil {
   502  		c.Status(http.StatusInternalServerError)
   503  		_ = c.Error(utils.ErrInternalServer.NewWithNoMessage())
   504  		return
   505  	}
   506  
   507  	archives := make([]Archive, 0)
   508  
   509  	for _, meta := range metas {
   510  		archives = append(archives, Archive{
   511  			UID:       meta.UID,
   512  			Kind:      v1alpha1.KindWorkflow,
   513  			Namespace: meta.Namespace,
   514  			Name:      meta.Name,
   515  			CreatedAt: meta.CreatedAt,
   516  		})
   517  	}
   518  
   519  	c.JSON(http.StatusOK, archives)
   520  }
   521  
   522  // @Summary Get the detail of an archived workflow.
   523  // @Description Get the detail of an archived workflow.
   524  // @Tags archives
   525  // @Produce json
   526  // @Param uid query string true "uid"
   527  // @Success 200 {object} Detail
   528  // @Router /archives/workflows/{uid} [get]
   529  // @Failure 500 {object} utils.APIError
   530  func (s *Service) detailWorkflow(c *gin.Context) {
   531  	var (
   532  		err    error
   533  		detail Detail
   534  	)
   535  	uid := c.Param("uid")
   536  
   537  	if uid == "" {
   538  		c.Status(http.StatusBadRequest)
   539  		_ = c.Error(utils.ErrInvalidRequest.New("uid cannot be empty"))
   540  		return
   541  	}
   542  
   543  	meta, err := s.workflowStore.FindByUID(context.Background(), uid)
   544  	if err != nil {
   545  		if gorm.IsRecordNotFoundError(err) {
   546  			c.Status(http.StatusInternalServerError)
   547  			_ = c.Error(utils.ErrInvalidRequest.New("the archive schedule is not found"))
   548  		} else {
   549  			c.Status(http.StatusInternalServerError)
   550  			_ = c.Error(utils.ErrInternalServer.NewWithNoMessage())
   551  		}
   552  		return
   553  	}
   554  
   555  	workflow := &v1alpha1.Workflow{}
   556  	if err := json.Unmarshal([]byte(meta.Workflow), &workflow); err != nil {
   557  		c.Status(http.StatusInternalServerError)
   558  		_ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
   559  		return
   560  	}
   561  
   562  	detail = Detail{
   563  		Archive: Archive{
   564  			UID:       meta.UID,
   565  			Kind:      v1alpha1.KindWorkflow,
   566  			Name:      meta.Name,
   567  			Namespace: meta.Namespace,
   568  			CreatedAt: meta.CreatedAt,
   569  		},
   570  		KubeObject: core.KubeObjectDesc{
   571  			TypeMeta: metav1.TypeMeta{
   572  				APIVersion: workflow.APIVersion,
   573  				Kind:       workflow.Kind,
   574  			},
   575  			Meta: core.KubeObjectMeta{
   576  				Name:        workflow.Name,
   577  				Namespace:   workflow.Namespace,
   578  				Labels:      workflow.Labels,
   579  				Annotations: workflow.Annotations,
   580  			},
   581  			Spec: workflow.Spec,
   582  		},
   583  	}
   584  
   585  	c.JSON(http.StatusOK, detail)
   586  }
   587  
   588  // @Summary Delete the specified archived workflow.
   589  // @Description Delete the specified archived workflow.
   590  // @Tags archives
   591  // @Produce json
   592  // @Param uid path string true "uid"
   593  // @Success 200 {object} StatusResponse
   594  // @Failure 500 {object} utils.APIError
   595  // @Router /archives/workflows/{uid} [delete]
   596  func (s *Service) deleteWorkflow(c *gin.Context) {
   597  	var (
   598  		err error
   599  	)
   600  
   601  	uid := c.Param("uid")
   602  
   603  	if err = s.workflowStore.DeleteByUID(context.Background(), uid); err != nil {
   604  		c.Status(http.StatusInternalServerError)
   605  		_ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
   606  	} else {
   607  		if err = s.event.DeleteByUID(context.Background(), uid); err != nil {
   608  			c.Status(http.StatusInternalServerError)
   609  			_ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
   610  		} else {
   611  			c.JSON(http.StatusOK, StatusResponse{Status: "success"})
   612  		}
   613  	}
   614  }
   615  
   616  // @Summary Delete the specified archived workflows.
   617  // @Description Delete the specified archived workflows.
   618  // @Tags archives
   619  // @Produce json
   620  // @Param uids query string true "uids"
   621  // @Success 200 {object} StatusResponse
   622  // @Failure 500 {object} utils.APIError
   623  // @Router /archives/workflows [delete]
   624  func (s *Service) batchDeleteWorkflow(c *gin.Context) {
   625  	var (
   626  		err      error
   627  		uidSlice []string
   628  	)
   629  
   630  	uids := c.Query("uids")
   631  	if uids == "" {
   632  		c.Status(http.StatusBadRequest)
   633  		_ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(fmt.Errorf("uids cannot be empty")))
   634  		return
   635  	}
   636  	uidSlice = strings.Split(uids, ",")
   637  
   638  	if err = s.workflowStore.DeleteByUIDs(context.Background(), uidSlice); err != nil {
   639  		_ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
   640  		c.Status(http.StatusInternalServerError)
   641  		return
   642  	}
   643  	if err = s.event.DeleteByUIDs(context.Background(), uidSlice); err != nil {
   644  		_ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
   645  		c.Status(http.StatusInternalServerError)
   646  		return
   647  	}
   648  
   649  	c.JSON(http.StatusOK, StatusResponse{Status: "success"})
   650  }
   651